What is a Server?

In a typical RAG system, we can break down the system into multiple functional modules, such as Retriever, Generator, etc. These modules perform different tasks and collaborate through some workflow orchestration to complete complex Q&A and reasoning tasks. In UR-2.0, based on the MCP (Model Context Protocol) architecture, we have unified the functional modules and proposed a more standardized implementation method — Server.
A Server is essentially a RAG module component with independent functionality.
Each Server encapsulates a core task logic (such as retrieval, generation, evaluation, etc.) and provides standardized interfaces externally through tool functions (Tool), supporting flexible scheduling and composition in a complete inference pipeline.

Quick Start: Developing a Server

To help you understand how to use a Server, we will demonstrate the complete development process of a simple “Calculator” module as an example.

Step1: Create Calculator Server

First, we instantiate a Server named calculator through UltraRAG_MCP_Server:
servers/calculator/src/calculator.py
from ultrarag_mcp.server import UltraRAG_MCP_Server

app = UltraRAG_MCP_Server("calculator")

if __name__ == "__main__":
    app.run(transport="stdio")

Step2: Implement Tool Functions

Register tool functions using the @app.tool decorator, which will be called in the pipeline:
servers/calculator/src/calculator.py
from ultrarag_mcp.server import UltraRAG_MCP_Server

app = UltraRAG_MCP_Server("calculator")

@app.tool(output="a,b->result")
def add(a: float, b: float) -> Dict[str, float]:
    """Return the sum of a and b"""
    return {"result": a + b}

@app.tool(output="result")
def minus(a: float, b: float) -> Dict[str, float]:
    """Return a - b"""
    return {"result": a - b}
    
@app.tool(output="a,b->None")
def log(a: float, b: float) -> None:
   """Logging a, b"""
   app.logger.info(f"a: {a}, b:{b}")

if __name__ == "__main__":
    app.run(transport="stdio")

Step3: Configure Parameter File

Create a parameter configuration file servers/calculator/parameter.yaml. Please write the parameters needed by your implemented tools into the parameter file under the server path, which is used to mark parameters and inject default values:
/images/yaml.svgservers/calculator/parameter.yaml
a: 1
b: 2

Notes: Parameter Registration Mechanism

UR-2.0 will automatically read the parameter.yaml file under each Server directory during the build phase and register the parameters required by the tool functions accordingly. This mechanism brings great convenience but also requires attention to the following points:
  • Support parameter sharing: If multiple tool functions need to share a parameter (such as template, path, etc.), declare it only once in parameter.yaml and reuse it without redefining.
  • Beware of field overwrite risks: If parameters required by multiple tools conflict in meaning or default values, use different field names explicitly to avoid being overwritten in the generated configuration file.
  • Context auto-inference mechanism: If some input parameters in the tool function are not present in parameter.yaml, UltraRAG will try to obtain them from the runtime context by default (i.e., considered as provided by the output of upstream Tools). Therefore, only parameters that cannot be automatically passed through context need to be explicitly written into parameter.yaml.

Encapsulation as a Class: Supporting State Management

In some scenarios, we may want to maintain some shared state or variables inside the Server, such as initializing a model. You can encapsulate the Server as a class. Below is an example of encapsulating the Calculator Server as a class:
servers/calculator/src/calculator.py
from ultrarag_mcp.server import UltraRAG_MCP_Server

app = UltraRAG_MCP_Server("calculator")

class Calculator:
    def __init__(self, mcp_inst: UltraRAG_MCP_Server)
        mcp_inst.tool(self.add, output="b->result")
        mcp_inst.tool(self.minus, output="result")
        mcp_inst.tool(self.log, output="none")
        self.a = 10  # Simulate global variable
        
    def add(self, b: float) -> Dict[str, float]:
        return {"result": self.a + b}
        
    def minus(self, b: float) -> Dict[str, float]:
        return {"result": self.a - b}
    
    def log(self, b: float):
        app.logger.info(f"a: {a}, b:{b}")
 
if __name__ == "__main__":
    Calculator(app)
    app.run(transport="stdio")
The above content shows how to develop a custom Server and implement tool functions (Tool) within it. You can use this approach to encapsulate any functionality as a module component and compose calls in the UR-2.0 pipeline. In the next section, we will introduce several commonly used built-in Servers in UR-2.0 to help you quickly build your own RAG workflows.