Files
connpy/docs/connpy/mcp_client.html
T
2026-05-28 18:22:00 -03:00

356 lines
18 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.5">
<title>connpy.mcp_client API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>connpy.mcp_client</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="connpy.mcp_client.MCPClientManager"><code class="flex name class">
<span>class <span class="ident">MCPClientManager</span></span>
<span>(</span><span>config=None)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class MCPClientManager:
&#34;&#34;&#34;Manages MCP SSE client connections for connpy.&#34;&#34;&#34;
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
with cls._lock:
if cls._instance is None:
cls._instance = super(MCPClientManager, cls).__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, config=None):
if self._initialized:
return
self.config = config
self.sessions: Dict[str, Dict[str, Any]] = {} # name -&gt; {session, stack}
self.tool_cache: Dict[str, List[Dict[str, Any]]] = {}
self._connecting: Dict[str, asyncio.Future] = {}
self._initialized = True
async def get_tools_for_llm(self, os_filter: Optional[str] = None) -&gt; List[Dict[str, Any]]:
&#34;&#34;&#34;
Fetches tools from enabled MCP servers that match the OS filter.
&#34;&#34;&#34;
if not MCP_AVAILABLE:
return []
all_llm_tools = []
try:
if hasattr(self.config, &#34;get_effective_setting&#34;):
mcp_config = self.config.get_effective_setting(&#34;ai&#34;, {}).get(&#34;mcp_servers&#34;, {})
else:
mcp_config = self.config.config.get(&#34;ai&#34;, {}).get(&#34;mcp_servers&#34;, {}) if hasattr(self.config, &#34;config&#34;) else {}
except Exception:
return []
async def _fetch(name, cfg):
if not cfg.get(&#34;enabled&#34;, True): return []
# Filter by OS if specified in config (primarily used for copilot strict matching)
auto_os = cfg.get(&#34;auto_load_on_os&#34;)
if os_filter is not None and auto_os and os_filter.lower() != auto_os.lower():
return []
try:
session = await self._ensure_connected(name, cfg)
if session:
if name in self.tool_cache: return self.tool_cache[name]
llm_tools = await self._fetch_tools_as_openai(name, session)
self.tool_cache[name] = llm_tools
return llm_tools
except Exception:
pass
return []
tasks = [ _fetch(name, cfg) for name, cfg in mcp_config.items() ]
if tasks:
results = await asyncio.gather(*tasks)
for tools in results:
all_llm_tools.extend(tools)
return all_llm_tools
async def _ensure_connected(self, name: str, cfg: Dict[str, Any]) -&gt; Optional[Any]:
if not MCP_AVAILABLE: return None
if name in self.sessions and self.sessions[name].get(&#34;session&#34;):
return self.sessions[name][&#34;session&#34;]
url = cfg.get(&#34;url&#34;)
if not url:
return None
if name in self._connecting:
try:
return await asyncio.wait_for(asyncio.shield(self._connecting[name]), timeout=10.0)
except Exception:
return None
loop = asyncio.get_running_loop()
fut = loop.create_future()
self._connecting[name] = fut
try:
from contextlib import AsyncExitStack
stack = AsyncExitStack()
async def _do_connect():
read, write = await stack.enter_async_context(sse_client(url))
session = await stack.enter_async_context(ClientSession(read, write))
await session.initialize()
return session
session = await asyncio.wait_for(_do_connect(), timeout=15.0)
self.sessions[name] = {&#34;session&#34;: session, &#34;stack&#34;: stack}
fut.set_result(session)
return session
except Exception:
fut.set_result(None)
return None
finally:
if name in self._connecting:
del self._connecting[name]
async def _fetch_tools_as_openai(self, server_name: str, session: Any) -&gt; List[Dict[str, Any]]:
try:
result = await asyncio.wait_for(session.list_tools(), timeout=5.0)
openai_tools = []
for tool in result.tools:
# Use mcp_ prefix to ensure valid function name for LiteLLM/Gemini
prefixed_name = f&#34;mcp_{server_name}__{tool.name}&#34;
openai_tools.append({
&#34;type&#34;: &#34;function&#34;,
&#34;function&#34;: {
&#34;name&#34;: prefixed_name,
&#34;description&#34;: f&#34;[{server_name}] {tool.description}&#34;,
&#34;parameters&#34;: tool.inputSchema
}
})
return openai_tools
except Exception:
return []
async def call_tool(self, full_tool_name: str, arguments: Dict[str, Any]) -&gt; Any:
&#34;&#34;&#34;Calls an MCP tool and returns text result.&#34;&#34;&#34;
if not MCP_AVAILABLE:
return &#34;Error: MCP SDK is not installed.&#34;
if &#34;__&#34; not in full_tool_name:
return f&#34;Error: Tool {full_tool_name} is not a valid MCP tool.&#34;
clean_name = full_tool_name[4:] if full_tool_name.startswith(&#34;mcp_&#34;) else full_tool_name
server_name, tool_name = clean_name.split(&#34;__&#34;, 1)
if server_name not in self.sessions:
return f&#34;Error: MCP server {server_name} is not connected.&#34;
session = self.sessions[server_name][&#34;session&#34;]
try:
result = await asyncio.wait_for(session.call_tool(tool_name, arguments), timeout=60.0)
text_outputs = [content.text for content in result.content if hasattr(content, &#34;text&#34;)]
return &#34;\n&#34;.join(text_outputs) if text_outputs else str(result)
except Exception as e:
return f&#34;Error calling tool {tool_name} on {server_name}: {str(e)}&#34;
async def shutdown(self):
&#34;&#34;&#34;Close all SSE connections.&#34;&#34;&#34;
for name, data in self.sessions.items():
stack = data.get(&#34;stack&#34;)
if stack:
await stack.aclose()
self.sessions = {}</code></pre>
</details>
<div class="desc"><p>Manages MCP SSE client connections for connpy.</p></div>
<h3>Methods</h3>
<dl>
<dt id="connpy.mcp_client.MCPClientManager.call_tool"><code class="name flex">
<span>async def <span class="ident">call_tool</span></span>(<span>self, full_tool_name: str, arguments: Dict[str, Any]) > Any</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">async def call_tool(self, full_tool_name: str, arguments: Dict[str, Any]) -&gt; Any:
&#34;&#34;&#34;Calls an MCP tool and returns text result.&#34;&#34;&#34;
if not MCP_AVAILABLE:
return &#34;Error: MCP SDK is not installed.&#34;
if &#34;__&#34; not in full_tool_name:
return f&#34;Error: Tool {full_tool_name} is not a valid MCP tool.&#34;
clean_name = full_tool_name[4:] if full_tool_name.startswith(&#34;mcp_&#34;) else full_tool_name
server_name, tool_name = clean_name.split(&#34;__&#34;, 1)
if server_name not in self.sessions:
return f&#34;Error: MCP server {server_name} is not connected.&#34;
session = self.sessions[server_name][&#34;session&#34;]
try:
result = await asyncio.wait_for(session.call_tool(tool_name, arguments), timeout=60.0)
text_outputs = [content.text for content in result.content if hasattr(content, &#34;text&#34;)]
return &#34;\n&#34;.join(text_outputs) if text_outputs else str(result)
except Exception as e:
return f&#34;Error calling tool {tool_name} on {server_name}: {str(e)}&#34;</code></pre>
</details>
<div class="desc"><p>Calls an MCP tool and returns text result.</p></div>
</dd>
<dt id="connpy.mcp_client.MCPClientManager.get_tools_for_llm"><code class="name flex">
<span>async def <span class="ident">get_tools_for_llm</span></span>(<span>self, os_filter: str | None = None) > List[Dict[str, Any]]</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">async def get_tools_for_llm(self, os_filter: Optional[str] = None) -&gt; List[Dict[str, Any]]:
&#34;&#34;&#34;
Fetches tools from enabled MCP servers that match the OS filter.
&#34;&#34;&#34;
if not MCP_AVAILABLE:
return []
all_llm_tools = []
try:
if hasattr(self.config, &#34;get_effective_setting&#34;):
mcp_config = self.config.get_effective_setting(&#34;ai&#34;, {}).get(&#34;mcp_servers&#34;, {})
else:
mcp_config = self.config.config.get(&#34;ai&#34;, {}).get(&#34;mcp_servers&#34;, {}) if hasattr(self.config, &#34;config&#34;) else {}
except Exception:
return []
async def _fetch(name, cfg):
if not cfg.get(&#34;enabled&#34;, True): return []
# Filter by OS if specified in config (primarily used for copilot strict matching)
auto_os = cfg.get(&#34;auto_load_on_os&#34;)
if os_filter is not None and auto_os and os_filter.lower() != auto_os.lower():
return []
try:
session = await self._ensure_connected(name, cfg)
if session:
if name in self.tool_cache: return self.tool_cache[name]
llm_tools = await self._fetch_tools_as_openai(name, session)
self.tool_cache[name] = llm_tools
return llm_tools
except Exception:
pass
return []
tasks = [ _fetch(name, cfg) for name, cfg in mcp_config.items() ]
if tasks:
results = await asyncio.gather(*tasks)
for tools in results:
all_llm_tools.extend(tools)
return all_llm_tools</code></pre>
</details>
<div class="desc"><p>Fetches tools from enabled MCP servers that match the OS filter.</p></div>
</dd>
<dt id="connpy.mcp_client.MCPClientManager.shutdown"><code class="name flex">
<span>async def <span class="ident">shutdown</span></span>(<span>self)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">async def shutdown(self):
&#34;&#34;&#34;Close all SSE connections.&#34;&#34;&#34;
for name, data in self.sessions.items():
stack = data.get(&#34;stack&#34;)
if stack:
await stack.aclose()
self.sessions = {}</code></pre>
</details>
<div class="desc"><p>Close all SSE connections.</p></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="connpy" href="index.html">connpy</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="connpy.mcp_client.MCPClientManager" href="#connpy.mcp_client.MCPClientManager">MCPClientManager</a></code></h4>
<ul class="">
<li><code><a title="connpy.mcp_client.MCPClientManager.call_tool" href="#connpy.mcp_client.MCPClientManager.call_tool">call_tool</a></code></li>
<li><code><a title="connpy.mcp_client.MCPClientManager.get_tools_for_llm" href="#connpy.mcp_client.MCPClientManager.get_tools_for_llm">get_tools_for_llm</a></code></li>
<li><code><a title="connpy.mcp_client.MCPClientManager.shutdown" href="#connpy.mcp_client.MCPClientManager.shutdown">shutdown</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.5</a>.</p>
</footer>
</body>
</html>