mcp-use 1.2.12__py3-none-any.whl → 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mcp-use might be problematic. Click here for more details.

mcp_use/adapters/base.py CHANGED
@@ -5,7 +5,9 @@ This module provides the abstract base class that all MCP tool adapters should i
5
5
  """
6
6
 
7
7
  from abc import ABC, abstractmethod
8
- from typing import Any, TypeVar
8
+ from typing import TypeVar
9
+
10
+ from mcp.types import Prompt, Resource, Tool
9
11
 
10
12
  from ..client import MCPClient
11
13
  from ..connectors.base import BaseConnector
@@ -90,13 +92,13 @@ class BaseAdapter(ABC):
90
92
  return self._connector_tool_map[connector]
91
93
 
92
94
  # Create tools for this connector
93
- connector_tools = []
94
95
 
95
96
  # Make sure the connector is initialized and has tools
96
97
  success = await self._ensure_connector_initialized(connector)
97
98
  if not success:
98
99
  return []
99
100
 
101
+ connector_tools = []
100
102
  # Now create tools for each MCP tool
101
103
  for tool in connector.tools:
102
104
  # Convert the tool and add it to the list if conversion was successful
@@ -104,6 +106,23 @@ class BaseAdapter(ABC):
104
106
  if converted_tool:
105
107
  connector_tools.append(converted_tool)
106
108
 
109
+ # Convert resources to tools so that agents can access resource content directly
110
+ resources_list = connector.resources or []
111
+ if resources_list:
112
+ for resource in resources_list:
113
+ converted_resource = self._convert_resource(resource, connector)
114
+ if converted_resource:
115
+ connector_tools.append(converted_resource)
116
+
117
+ # Convert prompts to tools so that agents can retrieve prompt content
118
+ prompts_list = connector.prompts or []
119
+ if prompts_list:
120
+ for prompt in prompts_list:
121
+ converted_prompt = self._convert_prompt(prompt, connector)
122
+ if converted_prompt:
123
+ connector_tools.append(converted_prompt)
124
+ # ------------------------------
125
+
107
126
  # Store the tools for this connector
108
127
  self._connector_tool_map[connector] = connector_tools
109
128
 
@@ -116,16 +135,18 @@ class BaseAdapter(ABC):
116
135
  return connector_tools
117
136
 
118
137
  @abstractmethod
119
- def _convert_tool(self, mcp_tool: dict[str, Any], connector: BaseConnector) -> T:
120
- """Convert an MCP tool to the target framework's tool format.
138
+ def _convert_tool(self, mcp_tool: Tool, connector: BaseConnector) -> T:
139
+ """Convert an MCP tool to the target framework's tool format."""
140
+ pass
121
141
 
122
- Args:
123
- mcp_tool: The MCP tool to convert.
124
- connector: The connector that provides this tool.
142
+ @abstractmethod
143
+ def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> T:
144
+ """Convert an MCP resource to the target framework's resource format."""
145
+ pass
125
146
 
126
- Returns:
127
- A tool in the target framework's format.
128
- """
147
+ @abstractmethod
148
+ def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> T:
149
+ """Convert an MCP prompt to the target framework's prompt format."""
129
150
  pass
130
151
 
131
152
  async def _create_tools_from_connectors(self, connectors: list[BaseConnector]) -> list[T]:
@@ -4,12 +4,21 @@ LangChain adapter for MCP tools.
4
4
  This module provides utilities to convert MCP tools to LangChain tools.
5
5
  """
6
6
 
7
+ import re
7
8
  from typing import Any, NoReturn
8
9
 
9
10
  from jsonschema_pydantic import jsonschema_to_pydantic
10
11
  from langchain_core.tools import BaseTool, ToolException
11
- from mcp.types import CallToolResult, EmbeddedResource, ImageContent, TextContent
12
- from pydantic import BaseModel
12
+ from mcp.types import (
13
+ CallToolResult,
14
+ EmbeddedResource,
15
+ ImageContent,
16
+ Prompt,
17
+ ReadResourceRequestParams,
18
+ Resource,
19
+ TextContent,
20
+ )
21
+ from pydantic import BaseModel, Field, create_model
13
22
 
14
23
  from ..connectors.base import BaseConnector
15
24
  from ..logging import logger
@@ -162,3 +171,104 @@ class LangChainAdapter(BaseAdapter):
162
171
  raise
163
172
 
164
173
  return McpToLangChainAdapter()
174
+
175
+ def _convert_resource(self, mcp_resource: Resource, connector: BaseConnector) -> BaseTool:
176
+ """Convert an MCP resource to LangChain's tool format.
177
+
178
+ Each resource becomes an async tool that returns its content when called.
179
+ The tool takes **no** arguments because the resource URI is fixed.
180
+ """
181
+
182
+ def _sanitize(name: str) -> str:
183
+ return re.sub(r"[^A-Za-z0-9_]+", "_", name).lower().strip("_")
184
+
185
+ class ResourceTool(BaseTool):
186
+ name: str = _sanitize(mcp_resource.name or f"resource_{mcp_resource.uri}")
187
+ description: str = (
188
+ mcp_resource.description
189
+ or f"Return the content of the resource located at URI {mcp_resource.uri}."
190
+ )
191
+ args_schema: type[BaseModel] = ReadResourceRequestParams
192
+ tool_connector: BaseConnector = connector
193
+ handle_tool_error: bool = True
194
+
195
+ def _run(self, **kwargs: Any) -> NoReturn:
196
+ raise NotImplementedError("Resource tools only support async operations")
197
+
198
+ async def _arun(self, **kwargs: Any) -> Any:
199
+ logger.debug(f'Resource tool: "{self.name}" called')
200
+ try:
201
+ result = await self.tool_connector.read_resource(mcp_resource.uri)
202
+ for content in result.contents:
203
+ # Attempt to decode bytes if necessary
204
+ if isinstance(content, bytes):
205
+ content_decoded = content.decode()
206
+ else:
207
+ content_decoded = str(content)
208
+
209
+ return content_decoded
210
+ except Exception as e:
211
+ if self.handle_tool_error:
212
+ return f"Error reading resource: {str(e)}"
213
+ raise
214
+
215
+ return ResourceTool()
216
+
217
+ def _convert_prompt(self, mcp_prompt: Prompt, connector: BaseConnector) -> BaseTool:
218
+ """Convert an MCP prompt to LangChain's tool format.
219
+
220
+ The resulting tool executes `get_prompt` on the connector with the prompt's name and
221
+ the user-provided arguments (if any). The tool returns the decoded prompt content.
222
+ """
223
+ prompt_arguments = mcp_prompt.arguments
224
+
225
+ # Sanitize the prompt name to create a valid Python identifier for the model name
226
+ base_model_name = re.sub(r"[^a-zA-Z0-9_]", "_", mcp_prompt.name)
227
+ if not base_model_name or base_model_name[0].isdigit():
228
+ base_model_name = "PromptArgs_" + base_model_name
229
+ dynamic_model_name = f"{base_model_name}_InputSchema"
230
+
231
+ if prompt_arguments:
232
+ field_definitions_for_create: dict[str, Any] = {}
233
+ for arg in prompt_arguments:
234
+ param_type: type = getattr(arg, "type", str)
235
+ if arg.required:
236
+ field_definitions_for_create[arg.name] = (
237
+ param_type,
238
+ Field(description=arg.description),
239
+ )
240
+ else:
241
+ field_definitions_for_create[arg.name] = (
242
+ param_type | None,
243
+ Field(None, description=arg.description),
244
+ )
245
+
246
+ InputSchema = create_model(
247
+ dynamic_model_name, **field_definitions_for_create, __base__=BaseModel
248
+ )
249
+ else:
250
+ # Create an empty Pydantic model if there are no arguments
251
+ InputSchema = create_model(dynamic_model_name, __base__=BaseModel)
252
+
253
+ class PromptTool(BaseTool):
254
+ name: str = mcp_prompt.name
255
+ description: str = mcp_prompt.description
256
+
257
+ args_schema: type[BaseModel] = InputSchema
258
+ tool_connector: BaseConnector = connector
259
+ handle_tool_error: bool = True
260
+
261
+ def _run(self, **kwargs: Any) -> NoReturn:
262
+ raise NotImplementedError("Prompt tools only support async operations")
263
+
264
+ async def _arun(self, **kwargs: Any) -> Any:
265
+ logger.debug(f'Prompt tool: "{self.name}" called with args: {kwargs}')
266
+ try:
267
+ result = await self.tool_connector.get_prompt(self.name, kwargs)
268
+ return result.messages
269
+ except Exception as e:
270
+ if self.handle_tool_error:
271
+ return f"Error fetching prompt: {str(e)}"
272
+ raise
273
+
274
+ return PromptTool()
@@ -9,6 +9,7 @@ import logging
9
9
  from collections.abc import AsyncIterator
10
10
 
11
11
  from langchain.agents import AgentExecutor, create_tool_calling_agent
12
+ from langchain.agents.output_parsers.tools import ToolAgentAction
12
13
  from langchain.globals import set_debug
13
14
  from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
14
15
  from langchain.schema import AIMessage, BaseMessage, HumanMessage, SystemMessage
@@ -21,7 +22,6 @@ from langchain_core.utils.input import get_color_mapping
21
22
 
22
23
  from mcp_use.client import MCPClient
23
24
  from mcp_use.connectors.base import BaseConnector
24
- from mcp_use.session import MCPSession
25
25
 
26
26
  from ..adapters.langchain_adapter import LangChainAdapter
27
27
  from ..logging import logger
@@ -102,9 +102,7 @@ class MCPAgent:
102
102
 
103
103
  # State tracking
104
104
  self._agent_executor: AgentExecutor | None = None
105
- self._sessions: dict[str, MCPSession] = {}
106
105
  self._system_message: SystemMessage | None = None
107
- self._tools: list[BaseTool] = []
108
106
 
109
107
  async def initialize(self) -> None:
110
108
  """Initialize the MCP client and agent."""
@@ -346,20 +344,18 @@ class MCPAgent:
346
344
  history_to_use = (
347
345
  external_history if external_history is not None else self._conversation_history
348
346
  )
349
- langchain_history: list[BaseMessage] = [
350
- m for m in history_to_use if isinstance(m, HumanMessage | AIMessage)
351
- ]
352
- inputs = {"input": query, "chat_history": langchain_history}
347
+ inputs = {"input": query, "chat_history": history_to_use}
353
348
 
354
349
  # 3. Stream & diff -------------------------------------------------------
355
- accumulated = ""
356
350
  async for event in self._agent_executor.astream_events(inputs):
351
+ if event.get("event") == "on_chain_end":
352
+ output = event["data"]["output"]
353
+ if isinstance(output, list):
354
+ for message in output:
355
+ if not isinstance(message, ToolAgentAction):
356
+ self.add_to_history(message)
357
357
  yield event
358
358
 
359
- # 4. Persist assistant message ------------------------------------------
360
- if self.memory_enabled and accumulated:
361
- self.add_to_history(AIMessage(content=accumulated))
362
-
363
359
  # 5. House-keeping -------------------------------------------------------
364
360
  if initialised_here and manage_connector:
365
361
  await self.close()
mcp_use/client.py CHANGED
@@ -12,6 +12,7 @@ from typing import Any
12
12
  from .config import create_connector_from_config, load_config_file
13
13
  from .logging import logger
14
14
  from .session import MCPSession
15
+ from .types.clientoptions import ClientOptions
15
16
 
16
17
 
17
18
  class MCPClient:
@@ -24,14 +25,17 @@ class MCPClient:
24
25
  def __init__(
25
26
  self,
26
27
  config: str | dict[str, Any] | None = None,
28
+ options: ClientOptions | None = None,
27
29
  ) -> None:
28
30
  """Initialize a new MCP client.
29
31
 
30
32
  Args:
31
33
  config: Either a dict containing configuration or a path to a JSON config file.
32
34
  If None, an empty configuration is used.
35
+ options: Configuration options for the client.
33
36
  """
34
37
  self.config: dict[str, Any] = {}
38
+ self.options = options or {}
35
39
  self.sessions: dict[str, MCPSession] = {}
36
40
  self.active_sessions: list[str] = []
37
41
 
@@ -43,22 +47,24 @@ class MCPClient:
43
47
  self.config = config
44
48
 
45
49
  @classmethod
46
- def from_dict(cls, config: dict[str, Any]) -> "MCPClient":
50
+ def from_dict(cls, config: dict[str, Any], options: ClientOptions | None = None) -> "MCPClient":
47
51
  """Create a MCPClient from a dictionary.
48
52
 
49
53
  Args:
50
54
  config: The configuration dictionary.
55
+ options: Optional client configuration options.
51
56
  """
52
- return cls(config=config)
57
+ return cls(config=config, options=options)
53
58
 
54
59
  @classmethod
55
- def from_config_file(cls, filepath: str) -> "MCPClient":
60
+ def from_config_file(cls, filepath: str, options: ClientOptions | None = None) -> "MCPClient":
56
61
  """Create a MCPClient from a configuration file.
57
62
 
58
63
  Args:
59
64
  filepath: The path to the configuration file.
65
+ options: Optional client configuration options.
60
66
  """
61
- return cls(config=load_config_file(filepath))
67
+ return cls(config=load_config_file(filepath), options=options)
62
68
 
63
69
  def add_server(
64
70
  self,
@@ -111,6 +117,7 @@ class MCPClient:
111
117
 
112
118
  Args:
113
119
  server_name: The name of the server to create a session for.
120
+ auto_initialize: Whether to automatically initialize the session.
114
121
 
115
122
  Returns:
116
123
  The created MCPSession.
@@ -128,7 +135,9 @@ class MCPClient:
128
135
  raise ValueError(f"Server '{server_name}' not found in config")
129
136
 
130
137
  server_config = servers[server_name]
131
- connector = create_connector_from_config(server_config)
138
+
139
+ # Create connector with options
140
+ connector = create_connector_from_config(server_config, options=self.options)
132
141
 
133
142
  # Create the session
134
143
  session = MCPSession(connector)
@@ -146,16 +155,16 @@ class MCPClient:
146
155
  self,
147
156
  auto_initialize: bool = True,
148
157
  ) -> dict[str, MCPSession]:
149
- """Create a session for the specified server.
158
+ """Create sessions for all configured servers.
150
159
 
151
160
  Args:
152
- auto_initialize: Whether to automatically initialize the session.
161
+ auto_initialize: Whether to automatically initialize the sessions.
153
162
 
154
163
  Returns:
155
- The created MCPSession. If server_name is None, returns the first created session.
164
+ Dictionary mapping server names to their MCPSession instances.
156
165
 
157
166
  Warns:
158
- Warning: If no servers are configured.
167
+ UserWarning: If no servers are configured.
159
168
  """
160
169
  # Get server config
161
170
  servers = self.config.get("mcpServers", {})
mcp_use/config.py CHANGED
@@ -7,7 +7,15 @@ This module provides functionality to load MCP configuration from JSON files.
7
7
  import json
8
8
  from typing import Any
9
9
 
10
- from .connectors import BaseConnector, HttpConnector, StdioConnector, WebSocketConnector
10
+ from .connectors import (
11
+ BaseConnector,
12
+ HttpConnector,
13
+ SandboxConnector,
14
+ StdioConnector,
15
+ WebSocketConnector,
16
+ )
17
+ from .connectors.utils import is_stdio_server
18
+ from .types.clientoptions import ClientOptions
11
19
 
12
20
 
13
21
  def load_config_file(filepath: str) -> dict[str, Any]:
@@ -23,23 +31,41 @@ def load_config_file(filepath: str) -> dict[str, Any]:
23
31
  return json.load(f)
24
32
 
25
33
 
26
- def create_connector_from_config(server_config: dict[str, Any]) -> BaseConnector:
34
+ def create_connector_from_config(
35
+ server_config: dict[str, Any],
36
+ options: ClientOptions | None = None,
37
+ ) -> BaseConnector:
27
38
  """Create a connector based on server configuration.
28
-
39
+ This function can be called with just the server_config parameter:
40
+ create_connector_from_config(server_config)
29
41
  Args:
30
42
  server_config: The server configuration section
43
+ options: Optional client configuration options including sandboxing preferences.
44
+ If None, default client options will be used.
31
45
 
32
46
  Returns:
33
47
  A configured connector instance
34
48
  """
49
+ # Use default options if none provided
50
+ options = options or {"is_sandboxed": False}
51
+
35
52
  # Stdio connector (command-based)
36
- if "command" in server_config and "args" in server_config:
53
+ if is_stdio_server(server_config) and not options.get("is_sandboxed", False):
37
54
  return StdioConnector(
38
55
  command=server_config["command"],
39
56
  args=server_config["args"],
40
57
  env=server_config.get("env", None),
41
58
  )
42
59
 
60
+ # Sandboxed connector
61
+ elif is_stdio_server(server_config) and options.get("is_sandboxed", False):
62
+ return SandboxConnector(
63
+ command=server_config["command"],
64
+ args=server_config["args"],
65
+ env=server_config.get("env", None),
66
+ e2b_options=options.get("sandbox_options", {}),
67
+ )
68
+
43
69
  # HTTP connector
44
70
  elif "url" in server_config:
45
71
  return HttpConnector(
@@ -5,9 +5,16 @@ This module provides interfaces for connecting to MCP implementations
5
5
  through different transport mechanisms.
6
6
  """
7
7
 
8
- from .base import BaseConnector
9
- from .http import HttpConnector
10
- from .stdio import StdioConnector
11
- from .websocket import WebSocketConnector
8
+ from .base import BaseConnector # noqa: F401
9
+ from .http import HttpConnector # noqa: F401
10
+ from .sandbox import SandboxConnector # noqa: F401
11
+ from .stdio import StdioConnector # noqa: F401
12
+ from .websocket import WebSocketConnector # noqa: F401
12
13
 
13
- __all__ = ["BaseConnector", "StdioConnector", "WebSocketConnector", "HttpConnector"]
14
+ __all__ = [
15
+ "BaseConnector",
16
+ "StdioConnector",
17
+ "HttpConnector",
18
+ "WebSocketConnector",
19
+ "SandboxConnector",
20
+ ]
@@ -9,7 +9,8 @@ from abc import ABC, abstractmethod
9
9
  from typing import Any
10
10
 
11
11
  from mcp import ClientSession
12
- from mcp.types import CallToolResult, Tool
12
+ from mcp.shared.exceptions import McpError
13
+ from mcp.types import CallToolResult, GetPromptResult, Prompt, ReadResourceResult, Resource, Tool
13
14
 
14
15
  from ..logging import logger
15
16
  from ..task_managers import ConnectionManager
@@ -26,6 +27,8 @@ class BaseConnector(ABC):
26
27
  self.client: ClientSession | None = None
27
28
  self._connection_manager: ConnectionManager | None = None
28
29
  self._tools: list[Tool] | None = None
30
+ self._resources: list[Resource] | None = None
31
+ self._prompts: list[Prompt] | None = None
29
32
  self._connected = False
30
33
 
31
34
  @abstractmethod
@@ -74,6 +77,8 @@ class BaseConnector(ABC):
74
77
 
75
78
  # Reset tools
76
79
  self._tools = None
80
+ self._resources = None
81
+ self._prompts = None
77
82
 
78
83
  if errors:
79
84
  logger.warning(f"Encountered {len(errors)} errors during resource cleanup")
@@ -88,21 +93,58 @@ class BaseConnector(ABC):
88
93
  # Initialize the session
89
94
  result = await self.client.initialize()
90
95
 
91
- # Get available tools
92
- tools_result = await self.client.list_tools()
93
- self._tools = tools_result.tools
94
-
95
- logger.debug(f"MCP session initialized with {len(self._tools)} tools")
96
+ server_capabilities = result.capabilities
97
+
98
+ if server_capabilities.tools:
99
+ # Get available tools
100
+ tools_result = await self.list_tools()
101
+ self._tools = tools_result or []
102
+ else:
103
+ self._tools = []
104
+
105
+ if server_capabilities.resources:
106
+ # Get available resources
107
+ resources_result = await self.list_resources()
108
+ self._resources = resources_result or []
109
+ else:
110
+ self._resources = []
111
+
112
+ if server_capabilities.prompts:
113
+ # Get available prompts
114
+ prompts_result = await self.list_prompts()
115
+ self._prompts = prompts_result or []
116
+ else:
117
+ self._prompts = []
118
+
119
+ logger.debug(
120
+ f"MCP session initialized with {len(self._tools)} tools, "
121
+ f"{len(self._resources)} resources, "
122
+ f"and {len(self._prompts)} prompts"
123
+ )
96
124
 
97
125
  return result
98
126
 
99
127
  @property
100
128
  def tools(self) -> list[Tool]:
101
129
  """Get the list of available tools."""
102
- if not self._tools:
130
+ if self._tools is None:
103
131
  raise RuntimeError("MCP client is not initialized")
104
132
  return self._tools
105
133
 
134
+ @property
135
+ def resources(self) -> list[Resource]:
136
+ """Get the list of available resources."""
137
+ if self._resources is None:
138
+ raise RuntimeError("MCP client is not initialized")
139
+ return self._resources
140
+
141
+ @property
142
+ def prompts(self) -> list[Prompt]:
143
+ """Get the list of available prompts."""
144
+ if self._prompts is None:
145
+ raise RuntimeError("MCP client is not initialized")
146
+ return self._prompts
147
+
106
148
  async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
107
149
  """Call an MCP tool with the given arguments."""
108
150
  if not self.client:
@@ -113,43 +155,64 @@ class BaseConnector(ABC):
113
155
  logger.debug(f"Tool '{name}' called with result: {result}")
114
156
  return result
115
157
 
116
- async def list_resources(self) -> list[dict[str, Any]]:
158
+ async def list_tools(self) -> list[Tool]:
159
+ """List all available tools from the MCP implementation."""
160
+ if not self.client:
161
+ raise RuntimeError("MCP client is not connected")
162
+
163
+ logger.debug("Listing tools")
164
+ try:
165
+ result = await self.client.list_tools()
166
+ return result.tools
167
+ except McpError as e:
168
+ logger.error(f"Error listing tools: {e}")
169
+ return []
170
+
171
+ async def list_resources(self) -> list[Resource]:
117
172
  """List all available resources from the MCP implementation."""
118
173
  if not self.client:
119
174
  raise RuntimeError("MCP client is not connected")
120
175
 
121
176
  logger.debug("Listing resources")
122
- resources = await self.client.list_resources()
123
- return resources
124
-
125
- async def read_resource(self, uri: str) -> tuple[bytes, str]:
177
+ try:
178
+ result = await self.client.list_resources()
179
+ return result.resources
180
+ except McpError as e:
181
+ logger.error(f"Error listing resources: {e}")
182
+ return []
183
+
184
+ async def read_resource(self, uri: str) -> ReadResourceResult:
126
185
  """Read a resource by URI."""
127
186
  if not self.client:
128
187
  raise RuntimeError("MCP client is not connected")
129
188
 
130
189
  logger.debug(f"Reading resource: {uri}")
131
- resource = await self.client.read_resource(uri)
132
- return resource.content, resource.mimeType
190
+ result = await self.client.read_resource(uri)
191
+ return result
133
192
 
134
- async def list_prompts(self) -> list[dict[str, Any]]:
193
+ async def list_prompts(self) -> list[Prompt]:
135
194
  """List all available prompts from the MCP implementation."""
136
195
  if not self.client:
137
196
  raise RuntimeError("MCP client is not connected")
138
197
 
139
198
  logger.debug("Listing prompts")
140
- prompts = await self.client.list_prompts()
141
- return prompts
199
+ try:
200
+ result = await self.client.list_prompts()
201
+ return result.prompts
202
+ except McpError as e:
203
+ logger.error(f"Error listing prompts: {e}")
204
+ return []
142
205
 
143
206
  async def get_prompt(
144
207
  self, name: str, arguments: dict[str, Any] | None = None
145
- ) -> tuple[bytes, str]:
208
+ ) -> GetPromptResult:
146
209
  """Get a prompt by name."""
147
210
  if not self.client:
148
211
  raise RuntimeError("MCP client is not connected")
149
212
 
150
213
  logger.debug(f"Getting prompt: {name}")
151
- prompt = await self.client.get_prompt(name, arguments)
152
- return prompt
214
+ result = await self.client.get_prompt(name, arguments)
215
+ return result
153
216
 
154
217
  async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
155
218
  """Send a raw request to the MCP implementation."""