PostHole
Compose Login
You are browsing us.zone2 in read-only mode. Log in to participate.
rss-bridge 2026-02-25T12:02:38+00:00

Building LLM-Friendly MCP Tools in RubyMine: Pagination, Filtering, and Error Design

RubyMine enhances the developer experience with context-aware search features that make navigating a Rails application seamless, a powerful analysis engine that detects problems in the source code, and integrated support for the most popular version control systems. With AI becoming increasingly popular among developers as a tool that helps them understand codebases or develop applications, […]


Intelligent Ruby and Rails IDE

Follow

  • Follow:

Download

RubyMine

Building LLM-Friendly MCP Tools in RubyMine: Pagination, Filtering, and Error Design

Daniel Domjan

RubyMine enhances the developer experience with context-aware search features that make navigating a Rails application seamless, a powerful analysis engine that detects problems in the source code, and integrated support for the most popular version control systems.

With AI becoming increasingly popular among developers as a tool that helps them understand codebases or develop applications, these RubyMine features provide an extra level of value. Indeed, with access to the functionality of the IDE and information about a given project, AI assistants can produce higher-quality results more efficiently.

To improve AI-assisted workflows, since 2025.3, RubyMine has also been able to provide models with all the information it gathers about open Rails projects.

In this blog post, we collected how we implemented the new Rails toolset and what we’ve learned about MCP tool design in the process from a software engineering perspective.

What Is Model Context Protocol (MCP)?

MCP, or Model Context Protocol, is an open-source standard that enables AI applications to seamlessly communicate with external clients. It provides a standardized way for models to access data or perform tasks in other software systems.

How MCP Servers Work in IntelliJ-Based IDEs

IDEs built on the IntelliJ Platform come with their own integrated MCP servers, making it easy for both internal and external applications, such as JetBrains AI Assistant or Claude Code, to interact with them. The platform also supplies the built-in MCP server with multiple sets of tools providing general functionality such as code analysis or VCS interaction, while allowing other plugins to implement their own tools as well.

[Toolsets supplied by the IntelliJ Platform and RubyMine]

RubyMine 2025.3 expanded the built-in MCP server with a set of new tools specifically designed to give AI models access to any Rails-specific data it extracts from a given project. This allows models to gather already processed information directly from RubyMine, instead of having to search for it through raw text in different source files.

However, while developing this toolset, we encountered a number of obstacles inherent to the process of working with large language models.

Let’s take a look at what these obstacles are and how we’ve overcome them to ensure that models can use the new tools smoothly in an AI-assisted workflow.

Context Window Limit

Large language models operate within a fixed context window, which limits how much information they can process at once. Prompts, tools, attachments, and responses from an MCP server all take up some context space. Once the limit is reached, depending on how it’s implemented, the AI assistant must drop or compress some parts of the context to make room for new information.

[The layout of a Large Language Model Context Window.]

Consider a large Ruby on Rails application such as GitLab. Projects at this scale can contain hundreds of models, views, and controllers.

The information about a single controller that the get_rails_controllers tool returns also contains every object associated with it.

"class": "Controller (/path/to/controller.rb:line:col)",
"isAbstract": false,
"managedViews": ["/path/to/view.html.erb"],
"managedPartialViews": ["/path/to/_view.html.erb"],
"managedLayouts":  ["/path/to/layout.html.erb"],
"correspondingModel": "Model (/path/to/model.rb:line:col)"

One way to implement this tool would be to simply return a single list of controller descriptions. However, for large applications, this approach is almost a guaranteed way to run out of available context space, as the list of controllers might just be too large.

[Returned tools not fitting in the context window.]

Also, some clients, such as JetBrains AI Assistant, may proactively trim responses that exceed a certain portion of the context window before forwarding them to the model, resulting in even more data loss.

Pagination Strategies: Offset vs Cursor

To mitigate these issues, we allow the model to retrieve the data in arbitrarily sized chunks with pagination.

get_rails_controllers(page, page_size)

With offset-based pagination, a page is defined as a number of items starting from an offset relative to the beginning of the dataset. Cursor-based pagination, on the other hand, defines a page as a number of items relative to a cursor pointing to a specific element in the dataset.

Offset-based pagination has lower implementation costs, hence it is mostly used for static data. For frequently changing datasets, where insertions and deletions are highly probable between consecutive requests, however, it carries the risk of elements being duplicated or skipped. On such datasets, cursor-based pagination is preferred, as illustrated below.

[Showcasing offset-based and cursor-based paginations.]

Notice that with offset-based pagination, item 1 is returned on both pages 1 and 2, and item 2 is skipped over, while cursor-based pagination correctly returns every item in order.

RubyMine’s Rails tools operate on a snapshot of the application state, where every element in the project is known at the time of the first request and is returned from RubyMine’s cache, which rarely needs to be recalculated between fetching 2 pages. Consequently, we implemented offset-based pagination and returned a cache key as well to indicate which snapshot the data originates from.

[The LLM receives two pages with a different cache key.]

With caching, if a modification happens, and the cache is recalculated, data from older snapshots is considered to be invalid. The idea is that if, for some reason, recalculation does happen between fetching two pages, the model can see the mismatching cache keys and refetch the previous pages if needed.

Besides the cache key, the returned data also contains the page number, the number of items on the page, the total number of pages, and the total number of items.

"summary": {
"page": 1,
"item_count": 10,
"total_pages": 13,
"total_items": 125,

Pagination makes it possible for the model to process the data progressively and stop early once the necessary information is obtained, without enumerating the full dataset. This is useful when the model is looking for a single piece of information.

[The LLM answers a question while using the rails toolset with early stopping.]

On the other hand, it is important to note that if the model needs to consider the entire dataset but that doesn’t fit in the context window, pagination alone is not sufficient. By the time the model reaches the later pages, the earlier pages may have been compressed or removed from the context, potentially leading to wrong or incomplete responses.

[Data is removed from the LLM context window due to reaching it's limits.]

Tool Call Limit

[...]


Original source

Reply