mcp-nequeo 2.7.26__tar.gz

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.
@@ -0,0 +1,19 @@
1
+ Copyright (c) Nequeo 2026 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-nequeo
3
+ Version: 2.7.26
4
+ Summary: Model Context Protocol (MCP) client and server implementation
5
+ Author-email: Drazen Zadravec <noreply@dznequeo.net>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/drazenzadravec/tools/tree/master/ai/mcp
8
+ Project-URL: Issues, https://github.com/drazenzadravec/tools/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Dynamic: license-file
15
+
16
+ # Model Context Protocol (MCP) Client and Server
17
+
18
+ Client supports Stdio and Streamable HTTP. Server supports Stdio and Streamable HTTP.
19
+
20
+ ## Install
21
+
22
+ pip install mcp-nequeo
23
+
@@ -0,0 +1,8 @@
1
+ # Model Context Protocol (MCP) Client and Server
2
+
3
+ Client supports Stdio and Streamable HTTP. Server supports Stdio and Streamable HTTP.
4
+
5
+ ## Install
6
+
7
+ pip install mcp-nequeo
8
+
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "mcp-nequeo"
3
+ version = "2.7.26"
4
+ authors = [
5
+ { name="Drazen Zadravec", email="noreply@dznequeo.net" },
6
+ ]
7
+ description = "Model Context Protocol (MCP) client and server implementation"
8
+ readme = "README.md"
9
+ requires-python = ">=3.9"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+ license = "MIT"
15
+ license-files = ["LICEN[CS]E*"]
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/drazenzadravec/tools/tree/master/ai/mcp"
19
+ Issues = "https://github.com/drazenzadravec/tools/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,563 @@
1
+ import httpx
2
+
3
+ from mcp import ClientSession, StdioServerParameters
4
+ from mcp.client.stdio import stdio_client
5
+ from mcp.client.streamable_http import streamablehttp_client
6
+
7
+ from datetime import timedelta
8
+ from pydantic import AnyUrl, TypeAdapter
9
+ from typing import Optional, Any, List, Union, Callable, Dict
10
+ from contextlib import AsyncExitStack, _AsyncGeneratorContextManager
11
+
12
+ from .McpTypes import McpTool, McpPrompt, McpResource, McpToolParameters
13
+
14
+ # Model context protocol client.
15
+ class McpClient:
16
+ """
17
+ Model context protocol client.
18
+ """
19
+ def __init__(self):
20
+ self.open = False
21
+ self.timeout: timedelta = timedelta(seconds=60) # default timeout 60 seconds
22
+ self.logEvent: Callable[[str, str, str, Any], None] | None = None
23
+
24
+ # Initialize session and client objects
25
+ self.session: Optional[ClientSession] = None
26
+ self.exit_stack = AsyncExitStack()
27
+
28
+ # init
29
+ self.tools: List[McpTool] = []
30
+ self.prompts: List[McpPrompt] = []
31
+ self.resources: List[McpResource] = []
32
+
33
+ def onEvent(self, event: Callable[[str, str, str, Any], None]) -> None:
34
+ """
35
+ subscribe to the on event.
36
+ Args:
37
+ event: the log event handler.
38
+ """
39
+ self.logEvent = event
40
+
41
+ def setTimeout(self, timeout: timedelta) -> None:
42
+ """
43
+ set the timeout in seconds (default timeout 60 seconds).
44
+ Args:
45
+ timeout: the timeout in seconds.
46
+ """
47
+ self.timeout = timeout
48
+
49
+ def isConnected(self) -> bool:
50
+ """
51
+ is connected to MCP server.
52
+
53
+ Return:
54
+ true if connected to server; else false.
55
+ """
56
+ return self.open
57
+
58
+ def getTools(self) -> List[McpTool]:
59
+ """
60
+ get the list of tools
61
+
62
+ Return:
63
+ the list of tools; else empty.
64
+ """
65
+ return self.tools
66
+
67
+ def getPrompts(self) -> List[McpPrompt]:
68
+ """
69
+ get the list of prompts
70
+
71
+ Return:
72
+ the list of prompts; else empty.
73
+ """
74
+ return self.prompts
75
+
76
+ def getResources(self) -> List[McpResource]:
77
+ """
78
+ get the list of resources
79
+
80
+ Return:
81
+ the list of resources; else empty.
82
+ """
83
+ return self.resources
84
+
85
+ async def callTool(self, name: str, args: Dict[str, Any] | None = None) -> Any | None:
86
+ """
87
+ call the tool.
88
+
89
+ Args:
90
+ name: the name of the tool
91
+ args: the arguments
92
+
93
+ Return:
94
+ the result; else none.
95
+ """
96
+ # if open.
97
+ if self.open:
98
+ return await self.session.call_tool(name, arguments = args, read_timeout_seconds = self.timeout)
99
+ else:
100
+ return None
101
+
102
+ async def callPrompt(self, name: str, args: Dict[str, str] | None = None) -> Any | None:
103
+ """
104
+ read the resource.
105
+
106
+ Args:
107
+ name: the name of the prompt
108
+ args: the arguments
109
+
110
+ Return:
111
+ the result; else none.
112
+ """
113
+ # if open.
114
+ if self.open:
115
+ return await self.session.get_prompt(name, arguments = args)
116
+ else:
117
+ return None
118
+
119
+ async def callResource(self, uri: AnyUrl) -> Any | None:
120
+ """
121
+ read the resource.
122
+
123
+ Args:
124
+ uri: the resource URI
125
+
126
+ Return:
127
+ the result; else none.
128
+ """
129
+ # if open.
130
+ if self.open:
131
+ return await self.session.read_resource(uri)
132
+ else:
133
+ return None
134
+
135
+ async def closeConnection(self):
136
+ """
137
+ disconnect from the MCP server.
138
+ """
139
+ self.session = None
140
+ self.tools = []
141
+ self.prompts = []
142
+ self.resources = []
143
+
144
+ try:
145
+ # close the stack.
146
+ await self.exit_stack.aclose()
147
+ except Exception as e:
148
+ if (self.logEvent):
149
+ self.logEvent("error", "mcpclient", "client close", e)
150
+
151
+ # closed
152
+ self.logEvent = None
153
+ self.open = False
154
+
155
+ async def openConnectionStdio(self, serverScriptPath: str):
156
+ """
157
+ connect to the MCP server.
158
+ start receiving messages on stdin and sending messages on stdout.
159
+
160
+ Args:
161
+ serverScriptPath: the server script full path
162
+ """
163
+
164
+ # if not open.
165
+ if not self.open:
166
+ try:
167
+
168
+ # only if JavaScript or Python
169
+ is_python = serverScriptPath.endswith('.py')
170
+ is_js = serverScriptPath.endswith('.js')
171
+
172
+ # if not JavaScript and not Python
173
+ if not (is_python or is_js):
174
+ raise ValueError("Server script must be a .py or .js file")
175
+
176
+ # the command to execute
177
+ command = "python" if is_python else "node"
178
+ server_params = StdioServerParameters(
179
+ command = command,
180
+ args = [serverScriptPath],
181
+ env = None
182
+ )
183
+
184
+ # open a connection to the MCP server.
185
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
186
+ self.read, self.write = stdio_transport
187
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.read, self.write))
188
+
189
+ # start session.
190
+ await self.session.initialize()
191
+
192
+ # client connected.
193
+ self.open = True
194
+
195
+ # request.
196
+ await self.requestTools()
197
+ await self.requestPrompts()
198
+ await self.requestResources()
199
+
200
+ except Exception as e:
201
+ self.open = False
202
+ if (self.logEvent):
203
+ self.logEvent("error", "open", "open connection stdio", e)
204
+ raise # Re-throws the same exception
205
+
206
+ async def openConnectionStdioCustom(self, command: str, argsList: List[str] | None = None, envList: Dict[str, str] | None = None):
207
+ """
208
+ connect to the MCP server.
209
+ start receiving messages on stdin and sending messages on stdout.
210
+
211
+ Args:
212
+ command: the executable to run to start the server.
213
+ argsList: command line arguments to pass to the executable.
214
+ envList: the environment to use when spawning the process.
215
+ """
216
+
217
+ # if not open.
218
+ if not self.open:
219
+ try:
220
+
221
+ # the command to execute
222
+ server_params = StdioServerParameters(
223
+ command = command,
224
+ args = argsList,
225
+ env = envList
226
+ )
227
+
228
+ # open a connection to the MCP server.
229
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
230
+ self.read, self.write = stdio_transport
231
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.read, self.write))
232
+
233
+ # start session.
234
+ await self.session.initialize()
235
+
236
+ # client connected.
237
+ self.open = True
238
+
239
+ # request.
240
+ await self.requestTools()
241
+ await self.requestPrompts()
242
+ await self.requestResources()
243
+
244
+ except Exception as e:
245
+ self.open = False
246
+ if (self.logEvent):
247
+ self.logEvent("error", "open", "open connection stdio custom", e)
248
+ raise # Re-throws the same exception
249
+
250
+ async def openConnectionStdioServerParam(self, server: StdioServerParameters):
251
+ """
252
+ connect to the MCP server.
253
+ start receiving messages on stdin and sending messages on stdout.
254
+
255
+ Args:
256
+ server: stdio server parameters.
257
+ """
258
+
259
+ # if not open.
260
+ if not self.open:
261
+ try:
262
+
263
+ # open a connection to the MCP server.
264
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server))
265
+ self.read, self.write = stdio_transport
266
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.read, self.write))
267
+
268
+ # start session.
269
+ await self.session.initialize()
270
+
271
+ # client connected.
272
+ self.open = True
273
+
274
+ # request.
275
+ await self.requestTools()
276
+ await self.requestPrompts()
277
+ await self.requestResources()
278
+
279
+ except Exception as e:
280
+ self.open = False
281
+ if (self.logEvent):
282
+ self.logEvent("error", "open", "open connection stdio server param", e)
283
+ raise # Re-throws the same exception
284
+
285
+ async def openConnectionHttp(self, serverUrl: str, requestInit: Dict[str, str] | None = None):
286
+ """
287
+ connect to the MCP server.
288
+ start receiving messages on streamable HTTP.
289
+ For remote servers, set up a Streamable HTTP transport that handles
290
+ both client requests and server-to-client notifications.
291
+
292
+ Args:
293
+ serverUrl: the server URL path.
294
+ requestInit: ustomizes HTTP requests to the server.
295
+
296
+ Example:
297
+ serverUrl: https://example.com/mcp
298
+ requestInit: {
299
+ 'Authorization': 'Bearer <secret>'
300
+ }
301
+ """
302
+
303
+ # if not open.
304
+ if not self.open:
305
+ try:
306
+ http_transport = None
307
+
308
+ # open a connection to the MCP server.
309
+ if(requestInit is None):
310
+ http_transport = await self.exit_stack.enter_async_context(
311
+ streamablehttp_client(serverUrl, timeout=self.timeout))
312
+ else:
313
+ http_transport = await self.exit_stack.enter_async_context(
314
+ streamablehttp_client(url=serverUrl, headers=requestInit, timeout=self.timeout))
315
+
316
+ # get streams
317
+ self.read, self.write, _, = http_transport
318
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.read, self.write))
319
+
320
+ # Initialize the connection
321
+ await self.session.initialize()
322
+
323
+ # client connected.
324
+ self.open = True
325
+
326
+ # request.
327
+ await self.requestTools()
328
+ await self.requestPrompts()
329
+ await self.requestResources()
330
+
331
+ except Exception as e:
332
+ self.open = False
333
+ if (self.logEvent):
334
+ self.logEvent("error", "open", "open connection http", e)
335
+ raise # Re-throws the same exception
336
+
337
+ async def openConnectionHttpCustom(self, serverUrl: str, headers: Dict[str, str] | None = None, auth: httpx.Auth | None = None):
338
+ """
339
+ connect to the MCP server.
340
+ start receiving messages on streamable HTTP.
341
+ For remote servers, set up a Streamable HTTP transport that handles
342
+ both client requests and server-to-client notifications.
343
+
344
+ Args:
345
+ serverUrl: the server URL path.
346
+ headers: ustomizes HTTP requests to the server.
347
+ auth: ustomizes HTTP authorization to the server.
348
+
349
+ Example:
350
+ serverUrl: https://example.com/mcp
351
+ headers: {
352
+ 'Authorization': 'Bearer <secret>'
353
+ }
354
+ """
355
+
356
+ # if not open.
357
+ if not self.open:
358
+ try:
359
+ http_transport = None
360
+
361
+ # open a connection to the MCP server.
362
+ http_transport = await self.exit_stack.enter_async_context(
363
+ streamablehttp_client(url=serverUrl, headers=headers, timeout=self.timeout, auth=auth))
364
+
365
+ # get streams
366
+ self.read, self.write, _, = http_transport
367
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.read, self.write))
368
+
369
+ # Initialize the connection
370
+ await self.session.initialize()
371
+
372
+ # client connected.
373
+ self.open = True
374
+
375
+ # request.
376
+ await self.requestTools()
377
+ await self.requestPrompts()
378
+ await self.requestResources()
379
+
380
+ except Exception as e:
381
+ self.open = False
382
+ if (self.logEvent):
383
+ self.logEvent("error", "open", "open connection http custom", e)
384
+ raise # Re-throws the same exception
385
+
386
+ async def openConnectionHttpTransport(self, transport: _AsyncGeneratorContextManager):
387
+ """
388
+ connect to the MCP server.
389
+ start receiving messages on streamable HTTP.
390
+ For remote servers, set up a Streamable HTTP transport that handles
391
+ both client requests and server-to-client notifications.
392
+
393
+ Args:
394
+ transport: the transport to use to connect to the server. A streamablehttp_client implementation.
395
+
396
+ Example:
397
+ from botocore.credentials import Credentials
398
+ from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client
399
+
400
+ creds = Credentials(access_key="KEY", secret_key="SECRET")
401
+
402
+ transport = aws_iam_streamablehttp_client(
403
+ endpoint="https://example.com/mcp",
404
+ aws_service="lambda",
405
+ aws_region="ap-southeast-2",
406
+ credentials=creds
407
+ )
408
+ """
409
+
410
+ # if not open.
411
+ if not self.open:
412
+ try:
413
+ http_transport = None
414
+
415
+ # open a connection to the MCP server.
416
+ http_transport = await self.exit_stack.enter_async_context(transport)
417
+
418
+ # get streams
419
+ self.read, self.write, _, = http_transport
420
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.read, self.write))
421
+
422
+ # Initialize the connection
423
+ await self.session.initialize()
424
+
425
+ # client connected.
426
+ self.open = True
427
+
428
+ # request.
429
+ await self.requestTools()
430
+ await self.requestPrompts()
431
+ await self.requestResources()
432
+
433
+ except Exception as e:
434
+ self.open = False
435
+ if (self.logEvent):
436
+ self.logEvent("error", "open", "open connection http custom", e)
437
+ raise # Re-throws the same exception
438
+
439
+ async def requestTools(self) -> bool:
440
+ """
441
+ request the tools list.
442
+
443
+ Return:
444
+ true if list call succeeded: else false.
445
+ """
446
+ haslist: bool = False
447
+
448
+ # if open.
449
+ if self.open:
450
+ self.tools = []
451
+
452
+ try:
453
+ # load all tools.
454
+ toolsResult = await self.session.list_tools()
455
+ if toolsResult is not None:
456
+ if toolsResult.tools is not None:
457
+ for tool in toolsResult.tools:
458
+ # create the tool model.
459
+ self.tools.append(McpTool(
460
+ tool.name,
461
+ tool.name,
462
+ tool.description,
463
+ tool.inputSchema,
464
+ McpToolParameters(tool.inputSchema)
465
+ ))
466
+
467
+ haslist = True
468
+ except Exception as e:
469
+ if (self.logEvent):
470
+ self.logEvent("error", "tools", "request tools", e)
471
+
472
+ return haslist
473
+
474
+ async def requestPrompts(self) -> bool:
475
+ """
476
+ request the prompts list.
477
+
478
+ Return:
479
+ true if list call succeeded: else false.
480
+ """
481
+ haslist: bool = False
482
+
483
+ # if open.
484
+ if self.open:
485
+ self.prompts = []
486
+
487
+ try:
488
+ # load all prompts.
489
+ promptsResult = await self.session.list_prompts()
490
+ if promptsResult is not None:
491
+ if promptsResult.prompts is not None:
492
+ for prompt in promptsResult.prompts:
493
+ # create the prompt model.
494
+ self.prompts.append(McpPrompt(
495
+ prompt.name,
496
+ prompt.name,
497
+ prompt.description,
498
+ prompt.arguments
499
+ ))
500
+
501
+ haslist = True
502
+ except Exception as e:
503
+ if (self.logEvent):
504
+ self.logEvent("error", "prompts", "request prompts", e)
505
+
506
+ return haslist
507
+
508
+ async def requestResources(self) -> bool:
509
+ """
510
+ request the resources list.
511
+
512
+ Return:
513
+ true if list call succeeded: else false.
514
+ """
515
+ haslist: bool = False
516
+
517
+ # if open.
518
+ if self.open:
519
+ self.resources = []
520
+
521
+ try:
522
+ # load all resources.
523
+ resourcesResult = await self.session.list_resources()
524
+ if resourcesResult is not None:
525
+ if resourcesResult.resources is not None:
526
+ for resource in resourcesResult.resources:
527
+ # create the resource model.
528
+ self.resources.append(McpResource(
529
+ resource.name,
530
+ resource.name,
531
+ resource.description,
532
+ resource.uri,
533
+ resource.mimeType
534
+ ))
535
+
536
+ haslist = True
537
+ except Exception as e:
538
+ haslist = False
539
+ if (self.logEvent):
540
+ self.logEvent("error", "resources", "request resources", e)
541
+
542
+ try:
543
+ # load all resources.
544
+ resourcesResultTemplates = await self.session.list_resource_templates()
545
+ if resourcesResultTemplates is not None:
546
+ if resourcesResultTemplates.resourceTemplates is not None:
547
+ for resource in resourcesResultTemplates.resourceTemplates:
548
+ # create the resource model.
549
+ self.resources.append(McpResource(
550
+ resource.name,
551
+ resource.name,
552
+ resource.description,
553
+ resource.uriTemplate,
554
+ resource.mimeType
555
+ ))
556
+
557
+ haslist = True
558
+ except Exception as e:
559
+ haslist = False
560
+ if (self.logEvent):
561
+ self.logEvent("error", "resources_templates", "request resource templates", e)
562
+
563
+ return haslist