Настройка авторизации доступа к MCP серверу при помощи OpenIG

Введение

Эта статья является продолжением статьи о защите MCP сервера при помощи стека OpenAM и OpenIG. В предыдущей статье мы добавили аутентификацию для доступа к возможностям MCP сервера. В этой статье мы добавим ограничение доступа MCP клиента к некоторым инструментам MCP сервера.

Описание проекта

Проект состоит из сервера аутентификации OpenAM, шлюза авторизации OpenIG и демонстрационного MCP сервера timeserver. MCP сервер timeserver имеет метод получения текущего времени и метод установки времени. Далее мы ограничим доступ к методу установки времени.

Настройка доступа к функционалу MCP сервера

Для этого добавим в исходный маршрут к MCP серверу в цепочку фильтров фильтр McpToolsFilter.

10-mcp.json

...
{
   "name": "McpToolsFilter",
   "type": "ScriptableFilter",
   "config": {
      "type": "application/x-groovy",
      "file": "McpToolsFilter.groovy",
      "args": {
         "deny": []
      }
   }
}
...

В параметре deny оставим пустой массив. Для тестов временно уберем требование аутентификации через OpenAM. Для этого закомментируйте пока в маршруте фильтры ProtectedResourceFilter и ConditionEnforcementFilter .

В папку openig-config/scripts добавьте скрипт McpToolsFilter.groovy

import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import org.forgerock.http.protocol.Request
import org.forgerock.http.protocol.Status

def generateMcpErrorResponse(status, entity) {
    def response = new Response()
    response.status = status
    response.headers['Content-Type'] = "application/json"
    response.setEntity(entity)
    return response
}

def filterResponse(requestMethod, response) {

    def slurper = new JsonSlurper()
    def responseObj = slurper.parseText(response.entity.getString())
    
    if(requestMethod == "tools/list") {
        logger.info("denied tools: {}, {}", deny, requestMethod)
        if(responseObj.result.tools) {
            responseObj.result.tools = responseObj.result.tools.findAll{ !deny.contains(it.name) }
        }
        logger.info("filtered response: {}", responseObj)

        def newEntity = JsonOutput.toJson(responseObj)
        response.entity.setString(newEntity)    
    } 
    return response
}

logger.info('request: {}', request.entity)

def slurper = new JsonSlurper()
def requestObj = slurper.parseText(request.entity.getString())
def requestMethod = requestObj.method

if (requestMethod == "tools/call") {
    if(deny.contains(requestObj.params.name)) {
        logger.info("method denied: {}", requestObj.params.name)
        def errorObject = [
            jsonrpc: "2.0",
            id     : requestObj.id,
            error  : [
                code   : -32602,
                message: "Unknown tool: invalid_tool_name",
                data   : "Tool not found: " + requestObj.params.name
            ]
        ]
        return generateMcpErrorResponse(Status.OK, JsonOutput.toJson(errorObject))
    }
}

return next.handle(context, request)
    .then({response -> 
        logger.info('response: {}', response.entity)
        response = filterResponse(requestMethod, response)
        return response
    })

Скрипт фильтрует список доступных инструментов указанных в настройке фильтра.

Если клиент пытается вызвать инструмент, который запрещен фильтром, возвращается ошибка с кодом -32602 в соответствии со спецификацией Model Context Protocol.

Выполним запрос к MCP серверу для получения доступных инструментов.

 curl -X POST  --location  "http://localhost:8081/mcp" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}' | json_pp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   789    0   714  100    75   112k  12102 --:--:-- --:--:-- --:--:--  128k
{
   "id" : 1,
   "jsonrpc" : "2.0",
   "result" : {
  "tools" : [
         {
            "annotations" : {
               "destructiveHint" : true,
               "idempotentHint" : false,
               "openWorldHint" : true,
               "readOnlyHint" : false,
               "title" : ""
            },
            "description" : "Returns current time in ISO 8601 format",
            "inputSchema" : {
               "properties" : {},
               "required" : [],
               "type" : "object"
            },
            "name" : "current_time_service",
            "title" : "current_time_service"
         },
         {
            "annotations" : {
               "destructiveHint" : true,
               "idempotentHint" : false,
               "openWorldHint" : true,
               "readOnlyHint" : false,
               "title" : ""
            },
            "description" : "Sets the current time in ISO 8601 format",
            "inputSchema" : {
               "properties" : {
                  "timeStr" : {
                     "description" : "new server time",
                     "type" : "string"
                  }
               },
               "required" : [
                  "timeStr"
               ],
               "type" : "object"
            },
            "name" : "set_current_time_service",
            "title" : "set_current_time_service"
         }
      ]
   }
}
 curl -X POST  --location  "http://localhost:8081/mcp" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}' | json_pp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   789    0   714  100    75   112k  12102 --:--:-- --:--:-- --:--:--  128k
{
   "id" : 1,
   "jsonrpc" : "2.0",
   "result" : {
  "tools" : [
         {
            "annotations" : {
               "destructiveHint" : true,
               "idempotentHint" : false,
               "openWorldHint" : true,
               "readOnlyHint" : false,
               "title" : ""
            },
            "description" : "Returns current time in ISO 8601 format",
            "inputSchema" : {
               "properties" : {},
               "required" : [],
               "type" : "object"
            },
            "name" : "current_time_service",
            "title" : "current_time_service"
         },
         {
            "annotations" : {
               "destructiveHint" : true,
               "idempotentHint" : false,
               "openWorldHint" : true,
               "readOnlyHint" : false,
               "title" : ""
            },
            "description" : "Sets the current time in ISO 8601 format",
            "inputSchema" : {
               "properties" : {
                  "timeStr" : {
                     "description" : "new server time",
                     "type" : "string"
                  }
               },
               "required" : [
                  "timeStr"
               ],
               "type" : "object"
            },
            "name" : "set_current_time_service",
            "title" : "set_current_time_service"
         }
      ]
   }
}

Как видно из ответа, MCP сервер предоставляет два инструмента current_time_service для получения текущего времени и set_current_time_service для установки времени.

set_current_time_service является небезопасной операцией. Поэтому давайте запретим ее вызов из MCP клиента.

Добавим set_current_time_service в список запрещенных для вызова инструментов в фильтре McpToolsFilter .

Попробуем получить список доступных инструментов:

...
{
   "name": "McpToolsFilter",
   "type": "ScriptableFilter",
   "config": {
      "type": "application/x-groovy",
      "file": "McpToolsFilter.groovy",
      "args": {
         "deny": ["set_current_time_service"]
      }
   }
}
...
curl -X POST  --location  "http://localhost:8081/mcp" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}' | json_pp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   416  100   341  100    75  14981   3295 --:--:-- --:--:-- --:--:-- 18909
{
   "id" : 1,
   "jsonrpc" : "2.0",
   "result" : {
      "tools" : [
         {
            "annotations" : {
               "destructiveHint" : true,
               "idempotentHint" : false,
               "openWorldHint" : true,
               "readOnlyHint" : false,
               "title" : ""
            },
            "description" : "Returns current time in ISO 8601 format",
            "inputSchema" : {
               "properties" : {},
               "required" : [],
               "type" : "object"
            },
            "name" : "current_time_service",
            "title" : "current_time_service"
         }
      ]
   }
}

Как видно из текста ответа, инструмента set_current_time_service больше нет в списке.

Теперь попробуем вызвать инструмент set_current_time_service напрямую.

curl -X POST  --location  "http://localhost:8081/mcp" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -d '{
  "jsonrpc": "2.0",
  "id": 9,
  "method": "tools/call",
  "params": {
    "_meta": {
      "progressToken": 9
    },
    "name": "set_current_time_service",
    "arguments": {
      "timeStr": "2026-01-20T10:31:35.903756042Z"
    }
  }
}' | json_pp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   382  100   142  100   240  17924  30295 --:--:-- --:--:-- --:--:-- 54571
{
   "error" : {
      "code" : -32602,
      "data" : "Tool not found: set_current_time_service",
      "message" : "Unknown tool: invalid_tool_name"
   },
   "id" : 9,
   "jsonrpc" : "2.0"
}

Раскомментируйте обратно фильтры ProtectedResourceFilter и ConditionEnforcementFilter .

По аналогии, вы можете ограничить доступ к другим инструментам, ресурсам или промптам MCP сервера, а так же добавить, в зависимости от требований вашей организации, политики RBAC, ABAC или более сложные. Подробнее о настройке OpenIG вы можете почитать в документации.