Skip to content

manager

Class info

Classes

Name Children Inherits
AudioBase64Content
llmling_agent.models.content
Audio from base64 data.
    ImageBase64Content
    llmling_agent.models.content
    Image from base64 data.
      MCPClient
      llmling_agent.mcp_server.client
      FastMCP-based client for communicating with MCP servers.
        MCPManager
        llmling_agent.mcp_server.manager
        Manages MCP server connections and tools.
          ResourceInfo
          llmling_agent_config.resources
          Information about an available resource.
            ResourceProvider
            llmling_agent.resource_providers.base
            Base class for resource providers.
            SSEMCPServerConfig
            llmling_agent_config.mcp_server
            MCP server using Server-Sent Events transport.
              • BaseMCPServerConfig
              StdioMCPServerConfig
              llmling_agent_config.mcp_server
              MCP server started via stdio.
                • BaseMCPServerConfig
                StreamableHTTPMCPServerConfig
                llmling_agent_config.mcp_server
                MCP server using StreamableHttp.
                  • BaseMCPServerConfig
                  UsageLimits
                  llmling_agent_providers.base
                  Limits on model usage.

                    🛈 DocStrings

                    Tool management for LLMling agents.

                    MCPManager

                    Bases: ResourceProvider

                    Manages MCP server connections and tools.

                    Source code in src/llmling_agent/mcp_server/manager.py
                     64
                     65
                     66
                     67
                     68
                     69
                     70
                     71
                     72
                     73
                     74
                     75
                     76
                     77
                     78
                     79
                     80
                     81
                     82
                     83
                     84
                     85
                     86
                     87
                     88
                     89
                     90
                     91
                     92
                     93
                     94
                     95
                     96
                     97
                     98
                     99
                    100
                    101
                    102
                    103
                    104
                    105
                    106
                    107
                    108
                    109
                    110
                    111
                    112
                    113
                    114
                    115
                    116
                    117
                    118
                    119
                    120
                    121
                    122
                    123
                    124
                    125
                    126
                    127
                    128
                    129
                    130
                    131
                    132
                    133
                    134
                    135
                    136
                    137
                    138
                    139
                    140
                    141
                    142
                    143
                    144
                    145
                    146
                    147
                    148
                    149
                    150
                    151
                    152
                    153
                    154
                    155
                    156
                    157
                    158
                    159
                    160
                    161
                    162
                    163
                    164
                    165
                    166
                    167
                    168
                    169
                    170
                    171
                    172
                    173
                    174
                    175
                    176
                    177
                    178
                    179
                    180
                    181
                    182
                    183
                    184
                    185
                    186
                    187
                    188
                    189
                    190
                    191
                    192
                    193
                    194
                    195
                    196
                    197
                    198
                    199
                    200
                    201
                    202
                    203
                    204
                    205
                    206
                    207
                    208
                    209
                    210
                    211
                    212
                    213
                    214
                    215
                    216
                    217
                    218
                    219
                    220
                    221
                    222
                    223
                    224
                    225
                    226
                    227
                    228
                    229
                    230
                    231
                    232
                    233
                    234
                    235
                    236
                    237
                    238
                    239
                    240
                    241
                    242
                    243
                    244
                    245
                    246
                    247
                    248
                    249
                    250
                    251
                    252
                    253
                    254
                    255
                    256
                    257
                    258
                    259
                    260
                    261
                    262
                    263
                    264
                    265
                    266
                    267
                    268
                    269
                    270
                    271
                    272
                    273
                    274
                    275
                    276
                    277
                    278
                    279
                    280
                    281
                    282
                    283
                    284
                    285
                    286
                    287
                    288
                    289
                    290
                    291
                    292
                    293
                    294
                    295
                    296
                    297
                    298
                    299
                    300
                    301
                    302
                    303
                    304
                    305
                    306
                    307
                    308
                    309
                    310
                    311
                    312
                    313
                    314
                    315
                    316
                    317
                    318
                    319
                    320
                    321
                    322
                    323
                    324
                    325
                    326
                    327
                    class MCPManager(ResourceProvider):
                        """Manages MCP server connections and tools."""
                    
                        def __init__(
                            self,
                            name: str = "mcp",
                            owner: str | None = None,
                            servers: Sequence[MCPServerConfig | str] | None = None,
                            context: NodeContext | None = None,
                            progress_handler: ProgressHandler | None = None,
                            accessible_roots: list[str] | None = None,
                        ):
                            super().__init__(name, owner=owner)
                            self.servers: list[MCPServerConfig] = []
                            for server in servers or []:
                                self.add_server_config(server)
                            self.context = context
                            self.clients: dict[str, MCPClient] = {}
                            self.exit_stack = AsyncExitStack()
                            self._progress_handler = progress_handler
                            self._accessible_roots = accessible_roots
                    
                        def add_server_config(self, server: MCPServerConfig | str):
                            """Add a new MCP server to the manager."""
                            match server:
                                case str() if server.startswith("http") and server.endswith("/sse"):
                                    resolved: MCPServerConfig = SSEMCPServerConfig(url=server)
                                case str() if server.startswith("http"):
                                    resolved = StreamableHTTPMCPServerConfig(url=server)
                                case str():
                                    resolved = StdioMCPServerConfig.from_string(server)
                                case _:
                                    resolved = server
                            self.servers.append(resolved)
                    
                        def __repr__(self) -> str:
                            return f"MCPManager({self.servers!r})"
                    
                        async def __aenter__(self) -> Self:
                            try:
                                # Setup directly provided servers and context servers concurrently
                                tasks = [self.setup_server(server) for server in self.servers]
                                if self.context and self.context.config and self.context.config.mcp_servers:
                                    tasks.extend(
                                        self.setup_server(server)
                                        for server in self.context.config.get_mcp_servers()
                                    )
                    
                                if tasks:
                                    await asyncio.gather(*tasks)
                    
                            except Exception as e:
                                # Clean up in case of error
                                await self.__aexit__(type(e), e, e.__traceback__)
                                msg = "Failed to initialize MCP manager"
                                raise RuntimeError(msg) from e
                    
                            return self
                    
                        async def __aexit__(self, *exc):
                            await self.cleanup()
                    
                        async def _elicitation_callback(
                            self,
                            message: str,
                            response_type: type[Any],
                            params: types.ElicitRequestParams,
                            context: RequestContext,
                        ) -> ElicitResult[dict[str, Any]] | dict[str, Any] | None:
                            """Handle elicitation requests from MCP server."""
                            from fastmcp.client.elicitation import ElicitResult
                            from mcp import types
                    
                            from llmling_agent.agent.context import AgentContext
                    
                            if self.context and isinstance(self.context, AgentContext):
                                legacy_result = await self.context.handle_elicitation(params)
                                # Convert legacy MCP result to FastMCP format
                                if isinstance(legacy_result, types.ElicitResult):
                                    if legacy_result.action == "accept" and legacy_result.content:
                                        return legacy_result.content
                                    return ElicitResult(action=legacy_result.action)
                                if isinstance(legacy_result, types.ErrorData):
                                    return ElicitResult(action="cancel")
                                return ElicitResult(action="decline")
                    
                            return ElicitResult(action="decline")
                    
                        async def _sampling_callback(
                            self,
                            messages: list[SamplingMessage],
                            params: types.CreateMessageRequestParams,
                            context: RequestContext,
                        ) -> str:
                            """Handle MCP sampling by creating a new agent with specified preferences."""
                            from mcp import types
                    
                            from llmling_agent.agent import Agent
                    
                            try:
                                # Convert messages to prompts for the agent
                                prompts: list[BaseContent | str] = []
                                for mcp_msg in messages:
                                    match mcp_msg.content:
                                        case types.TextContent(text=text):
                                            prompts.append(text)
                                        case types.ImageContent(data=data, mimeType=mime_type):
                                            our_image = ImageBase64Content(data=data, mime_type=mime_type)
                                            prompts.append(our_image)
                                        case types.AudioContent(data=data, mimeType=mime_type):
                                            fmt = mime_type.removeprefix("audio/")
                                            our_audio = AudioBase64Content(data=data, format=fmt)
                                            prompts.append(our_audio)
                    
                                # Extract model from preferences
                                model = None
                                if (
                                    params.modelPreferences
                                    and params.modelPreferences.hints
                                    and params.modelPreferences.hints[0].name
                                ):
                                    model = params.modelPreferences.hints[0].name
                    
                                # Create usage limits from sampling parameters
                                usage_limits = UsageLimits(
                                    output_tokens_limit=params.maxTokens,
                                    request_limit=1,  # Single sampling request
                                )
                    
                                # TODO: Apply temperature from params.temperature
                                # Currently no direct way to pass temperature to Agent constructor
                                # May need provider-level configuration or runtime model settings
                    
                                # Create agent with sampling parameters
                                agent: Agent[Any] = Agent(
                                    name="mcp-sampling-agent",
                                    model=model,
                                    system_prompt=params.systemPrompt or "",
                                    session=False,  # Don't store history for sampling
                                )
                    
                                async with agent:
                                    # Pass all prompts directly to the agent
                                    result = await agent.run(
                                        *prompts,
                                        store_history=False,
                                        usage_limits=usage_limits,
                                    )
                    
                                    return str(result.content)
                    
                            except Exception as e:
                                logger.exception("Sampling failed")
                                return f"Sampling failed: {e!s}"
                    
                        async def setup_server(self, config: MCPServerConfig) -> None:
                            """Set up a single MCP server connection."""
                            if not config.enabled:
                                return
                    
                            client = MCPClient(
                                elicitation_callback=self._elicitation_callback,
                                sampling_callback=self._sampling_callback,
                                progress_handler=self._progress_handler,
                                accessible_roots=self._accessible_roots,
                            )
                            client = await self.exit_stack.enter_async_context(client)
                            await client.connect(config)
                    
                            self.clients[config.client_id] = client
                    
                        async def get_tools(self) -> list[Tool]:
                            """Get all tools from all connected servers."""
                            all_tools: list[Tool] = []
                    
                            for client in self.clients.values():
                                for tool in client._available_tools:
                                    try:
                                        tool_info = client.convert_tool(tool)
                                        all_tools.append(tool_info)
                                        logger.debug("Registered MCP tool: %s", tool.name)
                                    except Exception:
                                        msg = "Failed to create tool from MCP tool: %s"
                                        logger.exception(msg, tool.name)
                                        continue
                    
                            return all_tools
                    
                        async def list_prompts(self) -> list[StaticPrompt]:
                            """Get all available prompts from MCP servers."""
                    
                            async def get_client_prompts(client: MCPClient) -> list[StaticPrompt]:
                                try:
                                    result = await client.list_prompts()
                                    client_prompts: list[StaticPrompt] = []
                                    for prompt in result:
                                        try:
                                            converted = await convert_mcp_prompt(client, prompt)
                                            client_prompts.append(converted)
                                        except Exception:
                                            logger.exception("Failed to convert prompt: %s", prompt.name)
                                except Exception:
                                    logger.exception("Failed to get prompts from MCP server")
                                    return []
                                else:
                                    return client_prompts
                    
                            results = await asyncio.gather(*[
                                get_client_prompts(client) for client in self.clients.values()
                            ])
                            return [prompt for client_prompts in results for prompt in client_prompts]
                    
                        async def list_resources(self) -> list[ResourceInfo]:
                            """Get all available resources from MCP servers."""
                    
                            async def get_client_resources(client: MCPClient) -> list[ResourceInfo]:
                                try:
                                    result = await client.list_resources()
                                    client_resources: list[ResourceInfo] = []
                                    for resource in result:
                                        try:
                                            converted = await convert_mcp_resource(resource)
                                            client_resources.append(converted)
                                        except Exception:
                                            logger.exception("Failed to convert resource: %s", resource.name)
                                except Exception:
                                    logger.exception("Failed to get resources from MCP server")
                                    return []
                                else:
                                    return client_resources
                    
                            results = await asyncio.gather(
                                *[get_client_resources(client) for client in self.clients.values()],
                                return_exceptions=False,
                            )
                            return [resource for client_resources in results for resource in client_resources]
                    
                        async def cleanup(self) -> None:
                            """Clean up all MCP connections."""
                            try:
                                try:
                                    # Clean up exit stack (which includes MCP clients)
                                    await self.exit_stack.aclose()
                                except RuntimeError as e:
                                    if "different task" in str(e):
                                        # Handle task context mismatch
                                        current_task = asyncio.current_task()
                                        if current_task:
                                            loop = asyncio.get_running_loop()
                                            await loop.create_task(self.exit_stack.aclose())
                                    else:
                                        raise
                    
                                self.clients.clear()
                    
                            except Exception as e:
                                msg = "Error during MCP manager cleanup"
                                logger.exception(msg, exc_info=e)
                                raise RuntimeError(msg) from e
                    
                        @property
                        def active_servers(self) -> list[str]:
                            """Get IDs of active servers."""
                            return list(self.clients)
                    

                    active_servers property

                    active_servers: list[str]
                    

                    Get IDs of active servers.

                    add_server_config

                    add_server_config(server: MCPServerConfig | str)
                    

                    Add a new MCP server to the manager.

                    Source code in src/llmling_agent/mcp_server/manager.py
                    86
                    87
                    88
                    89
                    90
                    91
                    92
                    93
                    94
                    95
                    96
                    97
                    def add_server_config(self, server: MCPServerConfig | str):
                        """Add a new MCP server to the manager."""
                        match server:
                            case str() if server.startswith("http") and server.endswith("/sse"):
                                resolved: MCPServerConfig = SSEMCPServerConfig(url=server)
                            case str() if server.startswith("http"):
                                resolved = StreamableHTTPMCPServerConfig(url=server)
                            case str():
                                resolved = StdioMCPServerConfig.from_string(server)
                            case _:
                                resolved = server
                        self.servers.append(resolved)
                    

                    cleanup async

                    cleanup() -> None
                    

                    Clean up all MCP connections.

                    Source code in src/llmling_agent/mcp_server/manager.py
                    301
                    302
                    303
                    304
                    305
                    306
                    307
                    308
                    309
                    310
                    311
                    312
                    313
                    314
                    315
                    316
                    317
                    318
                    319
                    320
                    321
                    322
                    async def cleanup(self) -> None:
                        """Clean up all MCP connections."""
                        try:
                            try:
                                # Clean up exit stack (which includes MCP clients)
                                await self.exit_stack.aclose()
                            except RuntimeError as e:
                                if "different task" in str(e):
                                    # Handle task context mismatch
                                    current_task = asyncio.current_task()
                                    if current_task:
                                        loop = asyncio.get_running_loop()
                                        await loop.create_task(self.exit_stack.aclose())
                                else:
                                    raise
                    
                            self.clients.clear()
                    
                        except Exception as e:
                            msg = "Error during MCP manager cleanup"
                            logger.exception(msg, exc_info=e)
                            raise RuntimeError(msg) from e
                    

                    get_tools async

                    get_tools() -> list[Tool]
                    

                    Get all tools from all connected servers.

                    Source code in src/llmling_agent/mcp_server/manager.py
                    235
                    236
                    237
                    238
                    239
                    240
                    241
                    242
                    243
                    244
                    245
                    246
                    247
                    248
                    249
                    250
                    async def get_tools(self) -> list[Tool]:
                        """Get all tools from all connected servers."""
                        all_tools: list[Tool] = []
                    
                        for client in self.clients.values():
                            for tool in client._available_tools:
                                try:
                                    tool_info = client.convert_tool(tool)
                                    all_tools.append(tool_info)
                                    logger.debug("Registered MCP tool: %s", tool.name)
                                except Exception:
                                    msg = "Failed to create tool from MCP tool: %s"
                                    logger.exception(msg, tool.name)
                                    continue
                    
                        return all_tools
                    

                    list_prompts async

                    list_prompts() -> list[StaticPrompt]
                    

                    Get all available prompts from MCP servers.

                    Source code in src/llmling_agent/mcp_server/manager.py
                    252
                    253
                    254
                    255
                    256
                    257
                    258
                    259
                    260
                    261
                    262
                    263
                    264
                    265
                    266
                    267
                    268
                    269
                    270
                    271
                    272
                    273
                    274
                    async def list_prompts(self) -> list[StaticPrompt]:
                        """Get all available prompts from MCP servers."""
                    
                        async def get_client_prompts(client: MCPClient) -> list[StaticPrompt]:
                            try:
                                result = await client.list_prompts()
                                client_prompts: list[StaticPrompt] = []
                                for prompt in result:
                                    try:
                                        converted = await convert_mcp_prompt(client, prompt)
                                        client_prompts.append(converted)
                                    except Exception:
                                        logger.exception("Failed to convert prompt: %s", prompt.name)
                            except Exception:
                                logger.exception("Failed to get prompts from MCP server")
                                return []
                            else:
                                return client_prompts
                    
                        results = await asyncio.gather(*[
                            get_client_prompts(client) for client in self.clients.values()
                        ])
                        return [prompt for client_prompts in results for prompt in client_prompts]
                    

                    list_resources async

                    list_resources() -> list[ResourceInfo]
                    

                    Get all available resources from MCP servers.

                    Source code in src/llmling_agent/mcp_server/manager.py
                    276
                    277
                    278
                    279
                    280
                    281
                    282
                    283
                    284
                    285
                    286
                    287
                    288
                    289
                    290
                    291
                    292
                    293
                    294
                    295
                    296
                    297
                    298
                    299
                    async def list_resources(self) -> list[ResourceInfo]:
                        """Get all available resources from MCP servers."""
                    
                        async def get_client_resources(client: MCPClient) -> list[ResourceInfo]:
                            try:
                                result = await client.list_resources()
                                client_resources: list[ResourceInfo] = []
                                for resource in result:
                                    try:
                                        converted = await convert_mcp_resource(resource)
                                        client_resources.append(converted)
                                    except Exception:
                                        logger.exception("Failed to convert resource: %s", resource.name)
                            except Exception:
                                logger.exception("Failed to get resources from MCP server")
                                return []
                            else:
                                return client_resources
                    
                        results = await asyncio.gather(
                            *[get_client_resources(client) for client in self.clients.values()],
                            return_exceptions=False,
                        )
                        return [resource for client_resources in results for resource in client_resources]
                    

                    setup_server async

                    setup_server(config: MCPServerConfig) -> None
                    

                    Set up a single MCP server connection.

                    Source code in src/llmling_agent/mcp_server/manager.py
                    219
                    220
                    221
                    222
                    223
                    224
                    225
                    226
                    227
                    228
                    229
                    230
                    231
                    232
                    233
                    async def setup_server(self, config: MCPServerConfig) -> None:
                        """Set up a single MCP server connection."""
                        if not config.enabled:
                            return
                    
                        client = MCPClient(
                            elicitation_callback=self._elicitation_callback,
                            sampling_callback=self._sampling_callback,
                            progress_handler=self._progress_handler,
                            accessible_roots=self._accessible_roots,
                        )
                        client = await self.exit_stack.enter_async_context(client)
                        await client.connect(config)
                    
                        self.clients[config.client_id] = client
                    

                    convert_mcp_prompt async

                    convert_mcp_prompt(client: MCPClient, prompt: Prompt) -> StaticPrompt
                    

                    Convert MCP prompt to StaticPrompt.

                    Source code in src/llmling_agent/mcp_server/manager.py
                    43
                    44
                    45
                    46
                    47
                    48
                    49
                    50
                    51
                    52
                    53
                    54
                    async def convert_mcp_prompt(client: MCPClient, prompt: MCPPrompt) -> StaticPrompt:
                        """Convert MCP prompt to StaticPrompt."""
                        from mcp.types import TextContent
                    
                        result = await client.get_prompt(prompt.name)
                        messages = [
                            PromptMessage(role="system", content=message.content.text)
                            for message in result.messages
                            if isinstance(message.content, TextContent | TextResourceContents)
                        ]
                        desc = prompt.description or "No description provided"
                        return StaticPrompt(name=prompt.name, description=desc, messages=messages)
                    

                    convert_mcp_resource async

                    convert_mcp_resource(resource: Resource) -> ResourceInfo
                    

                    Convert MCP resource to ResourceInfo.

                    Source code in src/llmling_agent/mcp_server/manager.py
                    57
                    58
                    59
                    60
                    61
                    async def convert_mcp_resource(resource: MCPResource) -> ResourceInfo:
                        """Convert MCP resource to ResourceInfo."""
                        return ResourceInfo(
                            name=resource.name, uri=str(resource.uri), description=resource.description
                        )