Mandraki Mandraki
Get started

Tool Discovery and Scope Filtering

How Mandraki's MCP server dynamically filters available tools based on your API token's scopes, and how to use tools/list to discover what your integration can do.

How Tool Discovery Works

When an MCP client sends a tools/list request, the Mandraki MCP server does not return a fixed list. Instead, it queries the internal ToolBroker — the same registry that powers Mandraki’s built-in AI assistant — and filters the results based on the requesting token’s scopes.

This means your integration only sees tools it is permitted to use. A token with tasks:read and tasks:write scopes will see task tools but not calendar or drive tools.

Scope-to-Module Mapping

API token scopes map to internal app modules. The MCP server uses this mapping to decide which tools to include in the tools/list response:

Scope prefixModules exposedExample tools
taskstaskstasks.create-task, tasks.list-tasks, tasks.update-task
calendarcalendarcalendar.list-events, calendar.create-event, calendar.check-availability
drive, filesdrivedrive.search-files, drive.get-file-info
channels, messageschat, messageschat.search-messages, chat.get-thread-context
mailmailmail.search-mail, mail.read-email, mail.compose-draft
callscallCall-related tools
teamsteamsTeam management tools
usersusersUser lookup tools

A single scope prefix can expose multiple modules. For example, channels exposes both chat and messages modules because channel history and message operations are closely related.

Example: tools/list Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

Response (token with tasks:read + tasks:write scopes)

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "tasks.create-task",
        "description": "Create a new task in a project",
        "inputSchema": {
          "type": "object",
          "properties": {
            "title": { "type": "string", "description": "Task title" },
            "projectId": { "type": "string", "description": "Project to create the task in" },
            "description": { "type": "string", "description": "Task description" },
            "assigneeId": { "type": "string", "description": "User ID to assign the task to" },
            "priority": { "type": "string", "enum": ["LOW", "MEDIUM", "HIGH", "URGENT"] },
            "dueDate": { "type": "string", "description": "Due date in ISO 8601 format" }
          },
          "required": ["title"]
        }
      },
      {
        "name": "tasks.list-tasks",
        "description": "List tasks, optionally filtered by project or assignee",
        "inputSchema": {
          "type": "object",
          "properties": {
            "projectId": { "type": "string" },
            "assigneeId": { "type": "string" },
            "status": { "type": "string", "enum": ["OPEN", "COMPLETED"] }
          }
        }
      },
      {
        "name": "tasks.update-task",
        "description": "Update an existing task",
        "inputSchema": {
          "type": "object",
          "properties": {
            "taskId": { "type": "string" },
            "title": { "type": "string" },
            "description": { "type": "string" },
            "priority": { "type": "string" },
            "dueDate": { "type": "string" },
            "assigneeId": { "type": "string" }
          },
          "required": ["taskId"]
        }
      },
      {
        "name": "tasks.complete-task",
        "description": "Mark a task as completed",
        "inputSchema": {
          "type": "object",
          "properties": {
            "taskId": { "type": "string" }
          },
          "required": ["taskId"]
        }
      },
      {
        "name": "tasks.get-my-tasks",
        "description": "Get tasks assigned to the current user",
        "inputSchema": {
          "type": "object",
          "properties": {}
        }
      },
      {
        "name": "tasks.add-comment",
        "description": "Add a comment to a task",
        "inputSchema": {
          "type": "object",
          "properties": {
            "taskId": { "type": "string" },
            "content": { "type": "string" }
          },
          "required": ["taskId", "content"]
        }
      }
    ]
  }
}

Filtering by App

You can pass an appId parameter to tools/list to filter by a specific app module:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {
    "appId": "calendar"
  }
}

This returns only calendar tools (assuming your token has calendar:read or calendar:write scopes).

Scope Enforcement on tools/call

Scope filtering is not just cosmetic. When you call tools/call, the server verifies that the tool’s module is within your token’s allowed scopes. If you attempt to call a tool outside your scopes, you get an error:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "calendar.create-event",
    "arguments": { "title": "Meeting" }
  }
}

Response (token lacks calendar:* scopes):

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Tool 'calendar.create-event' is not within this token's scopes"
      }
    ],
    "isError": true
  }
}

Tool Naming Convention

All tool names follow the pattern module.action:

  • tasks.create-task — module is tasks, action is create-task
  • drive.search-files — module is drive, action is search-files
  • chat.search-messages — module is chat, action is search-messages

The module prefix determines which scope is required. A tool in the tasks module requires a scope starting with tasks:.

Read vs Write Scopes

Some modules have separate read and write scopes:

  • tasks:read — allows tasks.list-tasks, tasks.get-my-tasks
  • tasks:write — allows tasks.create-task, tasks.update-task, tasks.complete-task, tasks.add-comment

Grant read scopes for monitoring and reporting integrations. Grant write scopes when the integration needs to create or modify data.