libentry 1.29__py3-none-any.whl → 1.30.1__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.
- libentry/mcp/service.py +46 -71
- libentry/mcp/types.py +89 -1
- {libentry-1.29.dist-info → libentry-1.30.1.dist-info}/METADATA +2 -1
- {libentry-1.29.dist-info → libentry-1.30.1.dist-info}/RECORD +9 -9
- {libentry-1.29.dist-info → libentry-1.30.1.dist-info}/LICENSE +0 -0
- {libentry-1.29.dist-info → libentry-1.30.1.dist-info}/WHEEL +0 -0
- {libentry-1.29.dist-info → libentry-1.30.1.dist-info}/entry_points.txt +0 -0
- {libentry-1.29.dist-info → libentry-1.30.1.dist-info}/top_level.txt +0 -0
- {libentry-1.29.dist-info → libentry-1.30.1.dist-info}/zip-safe +0 -0
libentry/mcp/service.py
CHANGED
@@ -12,6 +12,7 @@ from types import GeneratorType
|
|
12
12
|
from typing import Any, Callable, Dict, Generator, Iterable, List, Literal, Optional, Tuple, Union
|
13
13
|
|
14
14
|
from flask import Flask, request as flask_request
|
15
|
+
from flask_cors import CORS
|
15
16
|
from pydantic import BaseModel, Field, TypeAdapter
|
16
17
|
|
17
18
|
from libentry import json, logger
|
@@ -20,7 +21,7 @@ from libentry.mcp.api import APIInfo, list_api_info
|
|
20
21
|
from libentry.mcp.types import BlobResourceContents, CallToolRequestParams, CallToolResult, Implementation, \
|
21
22
|
InitializeRequestParams, InitializeResult, JSONRPCError, JSONRPCNotification, JSONRPCRequest, JSONRPCResponse, \
|
22
23
|
ListResourcesResult, ListToolsResult, MIME, ReadResourceRequestParams, ReadResourceResult, Resource, SSE, \
|
23
|
-
ServerCapabilities, SubroutineError, SubroutineResponse,
|
24
|
+
ServerCapabilities, SubroutineError, SubroutineResponse, TextResourceContents, Tool, ToolSchema, \
|
24
25
|
ToolsCapability
|
25
26
|
from libentry.schema import APISignature, get_api_signature, query_api
|
26
27
|
|
@@ -643,26 +644,10 @@ class ToolsService:
|
|
643
644
|
try:
|
644
645
|
response = route.handler.subroutine_adapter(params.arguments)
|
645
646
|
except Exception as e:
|
646
|
-
|
647
|
-
return CallToolResult(
|
648
|
-
content=[TextContent(text=error)],
|
649
|
-
isError=True
|
650
|
-
)
|
647
|
+
return CallToolResult.from_exception(e)
|
651
648
|
|
652
649
|
if not isinstance(response, GeneratorType):
|
653
|
-
|
654
|
-
text = json.dumps(response.error)
|
655
|
-
return CallToolResult(
|
656
|
-
content=[TextContent(text=text)],
|
657
|
-
isError=True
|
658
|
-
)
|
659
|
-
else:
|
660
|
-
result = response.result
|
661
|
-
text = json.dumps(result) if isinstance(result, (Dict, BaseModel)) else str(result)
|
662
|
-
return CallToolResult(
|
663
|
-
content=[TextContent(text=text)],
|
664
|
-
isError=False
|
665
|
-
)
|
650
|
+
return CallToolResult.from_subroutine_response(response)
|
666
651
|
else:
|
667
652
|
return self._iter_tool_results(response)
|
668
653
|
|
@@ -674,43 +659,17 @@ class ToolsService:
|
|
674
659
|
it = iter(responses)
|
675
660
|
while True:
|
676
661
|
response = next(it)
|
662
|
+
assert isinstance(response, SubroutineResponse)
|
663
|
+
yield CallToolResult.from_subroutine_response(response)
|
677
664
|
if response.error is not None:
|
678
|
-
text = json.dumps(response.error)
|
679
|
-
yield CallToolResult(
|
680
|
-
content=[TextContent(text=text)],
|
681
|
-
isError=True
|
682
|
-
)
|
683
665
|
break
|
684
|
-
else:
|
685
|
-
result = response.result
|
686
|
-
text = json.dumps(result) if isinstance(result, (Dict, BaseModel)) else str(result)
|
687
|
-
yield CallToolResult(
|
688
|
-
content=[TextContent(text=text)],
|
689
|
-
isError=False
|
690
|
-
)
|
691
666
|
except StopIteration as e:
|
692
667
|
response = e.value
|
693
668
|
if response is not None:
|
694
|
-
|
695
|
-
|
696
|
-
return CallToolResult(
|
697
|
-
content=[TextContent(text=text)],
|
698
|
-
isError=True
|
699
|
-
)
|
700
|
-
else:
|
701
|
-
result = response.result
|
702
|
-
text = json.dumps(result) if isinstance(result, (Dict, BaseModel)) else str(result)
|
703
|
-
return CallToolResult(
|
704
|
-
content=[TextContent(text=text)],
|
705
|
-
isError=False
|
706
|
-
)
|
669
|
+
assert isinstance(response, SubroutineResponse)
|
670
|
+
return CallToolResult.from_subroutine_response(response)
|
707
671
|
except Exception as e:
|
708
|
-
|
709
|
-
error = CallToolResult(
|
710
|
-
content=[TextContent(text=text)],
|
711
|
-
isError=True
|
712
|
-
)
|
713
|
-
yield error
|
672
|
+
yield CallToolResult.from_exception(e)
|
714
673
|
return None
|
715
674
|
|
716
675
|
|
@@ -815,8 +774,6 @@ class FlaskServer(Flask):
|
|
815
774
|
def __init__(self, service, options: Dict[str, Any]):
|
816
775
|
super().__init__(__name__)
|
817
776
|
self.options = options
|
818
|
-
self.access_control_allow_origin = self.options.get("access_control_allow_origin")
|
819
|
-
self.access_control_allow_methods = self.options.get("access_control_allow_methods")
|
820
777
|
|
821
778
|
self.service_routes = {}
|
822
779
|
self.builtin_routes = {}
|
@@ -879,20 +836,10 @@ class FlaskServer(Flask):
|
|
879
836
|
return routes
|
880
837
|
|
881
838
|
def ok(self, body: Union[str, Iterable[str], None], mimetype: str):
|
882
|
-
|
883
|
-
if self.access_control_allow_origin:
|
884
|
-
response.headers["Access-Control-Allow-Origin"] = "*"
|
885
|
-
if self.access_control_allow_methods:
|
886
|
-
response.headers["Access-Control-Allow-Methods"] = "GET, POST"
|
887
|
-
return response
|
839
|
+
return self.response_class(body, status=200, mimetype=mimetype)
|
888
840
|
|
889
841
|
def error(self, body: str, mimetype=MIME.plain.value):
|
890
|
-
|
891
|
-
if self.access_control_allow_origin:
|
892
|
-
response.headers["Access-Control-Allow-Origin"] = "*"
|
893
|
-
if self.access_control_allow_methods:
|
894
|
-
response.headers["Access-Control-Allow-Methods"] = "GET, POST"
|
895
|
-
return response
|
842
|
+
return self.response_class(body, status=500, mimetype=mimetype)
|
896
843
|
|
897
844
|
@api.get("/")
|
898
845
|
def index(self, name: str = None):
|
@@ -950,7 +897,25 @@ class GunicornApplication(BaseApplication):
|
|
950
897
|
service = self._create_service(self.service_type, self.service_config)
|
951
898
|
logger.info("Service initialized.")
|
952
899
|
|
953
|
-
|
900
|
+
app = FlaskServer(service, self.options)
|
901
|
+
|
902
|
+
cors_kwargs = {}
|
903
|
+
origins = self.options.get("access_control_allow_origin")
|
904
|
+
if origins:
|
905
|
+
cors_kwargs["origins"] = [m.strip() for m in origins.split(",")]
|
906
|
+
|
907
|
+
methods = self.options.get("access_control_allow_methods")
|
908
|
+
if methods:
|
909
|
+
cors_kwargs["methods"] = [m.strip() for m in methods.split(",")]
|
910
|
+
|
911
|
+
allow_headers = self.options.get("access_control_allow_headers")
|
912
|
+
if allow_headers:
|
913
|
+
cors_kwargs["allow_headers"] = [m.strip() for m in allow_headers.split(",")]
|
914
|
+
|
915
|
+
if cors_kwargs:
|
916
|
+
logger.info(f"CORS arguments: {cors_kwargs}")
|
917
|
+
CORS(app, **cors_kwargs)
|
918
|
+
return app
|
954
919
|
|
955
920
|
@staticmethod
|
956
921
|
def _create_service(service_type, service_config):
|
@@ -1037,6 +1002,11 @@ class RunServiceConfig(BaseModel):
|
|
1037
1002
|
description="Access control allow methods.",
|
1038
1003
|
default="GET, POST"
|
1039
1004
|
)
|
1005
|
+
access_control_allow_headers: Optional[str] = Field(
|
1006
|
+
title="Access control allow headers",
|
1007
|
+
description="Access control allow headers.",
|
1008
|
+
default="*"
|
1009
|
+
)
|
1040
1010
|
name: Optional[str] = Field(
|
1041
1011
|
title="服务实例名称",
|
1042
1012
|
description="服务实例名称,会在进程命令行中显示。",
|
@@ -1092,12 +1062,16 @@ def run_service(
|
|
1092
1062
|
setattr(run_config, name, value)
|
1093
1063
|
|
1094
1064
|
if run_config.name is None:
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1065
|
+
if hasattr(service_type, "__name__"):
|
1066
|
+
name = service_type.__name__
|
1067
|
+
if hasattr(service_type, "__module__"):
|
1068
|
+
module = service_type.__module__
|
1069
|
+
if module != "builtins":
|
1070
|
+
name = f"{module}.{name}"
|
1071
|
+
run_config.name = name
|
1072
|
+
else:
|
1073
|
+
run_config.name = ""
|
1074
|
+
run_config.name = f"{run_config.name}({run_config.host}:{run_config.port})"
|
1101
1075
|
|
1102
1076
|
logger.info("Starting gunicorn server.")
|
1103
1077
|
|
@@ -1125,6 +1099,7 @@ def run_service(
|
|
1125
1099
|
"ssl_context": ssl_context,
|
1126
1100
|
"access_control_allow_origin": run_config.access_control_allow_origin,
|
1127
1101
|
"access_control_allow_methods": run_config.access_control_allow_methods,
|
1102
|
+
"access_control_allow_headers": run_config.access_control_allow_headers,
|
1128
1103
|
"proc_name": run_config.name,
|
1129
1104
|
}
|
1130
1105
|
for name, value in options.items():
|
libentry/mcp/types.py
CHANGED
@@ -354,6 +354,36 @@ class TextContent(BaseModel):
|
|
354
354
|
model_config = ConfigDict(extra="allow")
|
355
355
|
|
356
356
|
|
357
|
+
class ImageContent(BaseModel):
|
358
|
+
"""Image content for a message."""
|
359
|
+
|
360
|
+
type: Literal["image"] = "image"
|
361
|
+
data: str
|
362
|
+
"""The base64-encoded image data."""
|
363
|
+
mimeType: str
|
364
|
+
"""
|
365
|
+
The MIME type of the image. Different providers may support different
|
366
|
+
image types.
|
367
|
+
"""
|
368
|
+
|
369
|
+
model_config = ConfigDict(extra="allow")
|
370
|
+
|
371
|
+
|
372
|
+
class AudioContent(BaseModel):
|
373
|
+
"""Audio content for a message."""
|
374
|
+
|
375
|
+
type: Literal["audio"]
|
376
|
+
data: str
|
377
|
+
"""The base64-encoded audio data."""
|
378
|
+
mimeType: str
|
379
|
+
"""
|
380
|
+
The MIME type of the audio. Different providers may support different
|
381
|
+
audio types.
|
382
|
+
"""
|
383
|
+
|
384
|
+
model_config = ConfigDict(extra="allow")
|
385
|
+
|
386
|
+
|
357
387
|
class CallToolRequestParams(BaseModel):
|
358
388
|
"""Parameters for calling a tool."""
|
359
389
|
|
@@ -366,9 +396,67 @@ class CallToolRequestParams(BaseModel):
|
|
366
396
|
class CallToolResult(BaseModel):
|
367
397
|
"""The server's response to a tool call."""
|
368
398
|
|
369
|
-
content: List[TextContent]
|
399
|
+
content: List[Union[TextContent, ImageContent, AudioContent]] = []
|
400
|
+
structuredContent: Optional[Dict[str, Any]] = None
|
370
401
|
isError: bool = False
|
371
402
|
|
403
|
+
@classmethod
|
404
|
+
def from_exception(cls, e: Exception):
|
405
|
+
error = SubroutineError.from_exception(e)
|
406
|
+
return cls(
|
407
|
+
content=[TextContent(text=error.message)],
|
408
|
+
structuredContent=error.model_dump(exclude_none=True),
|
409
|
+
isError=True
|
410
|
+
)
|
411
|
+
|
412
|
+
@classmethod
|
413
|
+
def from_subroutine_response(cls, response: SubroutineResponse):
|
414
|
+
if response.error is not None:
|
415
|
+
error = response.error
|
416
|
+
return cls(
|
417
|
+
content=[TextContent(text=error.message)],
|
418
|
+
structuredContent=error.model_dump(exclude_none=True),
|
419
|
+
isError=True
|
420
|
+
)
|
421
|
+
else:
|
422
|
+
result = response.result
|
423
|
+
if isinstance(result, Dict):
|
424
|
+
return cls(
|
425
|
+
content=[],
|
426
|
+
structuredContent=result,
|
427
|
+
isError=False
|
428
|
+
)
|
429
|
+
elif isinstance(result, BaseModel):
|
430
|
+
return cls(
|
431
|
+
content=[],
|
432
|
+
structuredContent=result.model_dump(exclude_none=False),
|
433
|
+
isError=False
|
434
|
+
)
|
435
|
+
elif isinstance(result, List):
|
436
|
+
return cls(
|
437
|
+
content=result,
|
438
|
+
structuredContent=None,
|
439
|
+
isError=False
|
440
|
+
)
|
441
|
+
elif isinstance(result, (TextContent, ImageContent, AudioContent)):
|
442
|
+
return cls(
|
443
|
+
content=[result],
|
444
|
+
structuredContent=None,
|
445
|
+
isError=False
|
446
|
+
)
|
447
|
+
elif isinstance(result, str):
|
448
|
+
return cls(
|
449
|
+
content=[TextContent(text=result)],
|
450
|
+
structuredContent=None,
|
451
|
+
isError=False
|
452
|
+
)
|
453
|
+
else:
|
454
|
+
return cls(
|
455
|
+
content=[TextContent(text=str(result))],
|
456
|
+
structuredContent=None,
|
457
|
+
isError=False
|
458
|
+
)
|
459
|
+
|
372
460
|
|
373
461
|
class Resource(BaseModel):
|
374
462
|
"""A known resource that the server is capable of reading."""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: libentry
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.30.1
|
4
4
|
Summary: Entries for experimental utilities.
|
5
5
|
Home-page: https://github.com/XoriieInpottn/libentry
|
6
6
|
Author: xi
|
@@ -16,6 +16,7 @@ Requires-Dist: numpy
|
|
16
16
|
Requires-Dist: urllib3
|
17
17
|
Requires-Dist: httpx
|
18
18
|
Requires-Dist: Flask
|
19
|
+
Requires-Dist: flask-cors
|
19
20
|
Requires-Dist: gunicorn
|
20
21
|
Dynamic: author
|
21
22
|
Dynamic: author-email
|
@@ -13,8 +13,8 @@ libentry/utils.py,sha256=vCm6UyAlibnPOlPJHZO57u3TXhw5PZmGM5_vBAPUnB4,1981
|
|
13
13
|
libentry/mcp/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
|
14
14
|
libentry/mcp/api.py,sha256=hhveOjDYhWiEEq3C7wSAOdpbPn9JP1T1CW3QYWrLHa4,3679
|
15
15
|
libentry/mcp/client.py,sha256=_O-O6OETwHidhiFmg7P01NIrVhHgEetwFeFfJNqRt6M,24899
|
16
|
-
libentry/mcp/service.py,sha256=
|
17
|
-
libentry/mcp/types.py,sha256=
|
16
|
+
libentry/mcp/service.py,sha256=vVlUa0uEXNkwrN78-_BK7NenRRjdVeW0hMJkeP8baPM,40867
|
17
|
+
libentry/mcp/types.py,sha256=5ddT_rxQyL1zvHDXnpzfGXMogviSNVrKd9ohd8yPZYc,15149
|
18
18
|
libentry/service/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
|
19
19
|
libentry/service/common.py,sha256=OVaW2afgKA6YqstJmtnprBCqQEUZEWotZ6tHavmJJeU,42
|
20
20
|
libentry/service/flask.py,sha256=2egCFFhRAfLpmSyibgaJ-3oexI-j27P1bmaPEn-hSlc,13817
|
@@ -22,10 +22,10 @@ libentry/service/list.py,sha256=ElHWhTgShGOhaxMUEwVbMXos0NQKjHsODboiQ-3AMwE,1397
|
|
22
22
|
libentry/service/running.py,sha256=FrPJoJX6wYxcHIysoatAxhW3LajCCm0Gx6l7__6sULQ,5105
|
23
23
|
libentry/service/start.py,sha256=mZT7b9rVULvzy9GTZwxWnciCHgv9dbGN2JbxM60OMn4,1270
|
24
24
|
libentry/service/stop.py,sha256=wOpwZgrEJ7QirntfvibGq-XsTC6b3ELhzRW2zezh-0s,1187
|
25
|
-
libentry-1.
|
26
|
-
libentry-1.
|
27
|
-
libentry-1.
|
28
|
-
libentry-1.
|
29
|
-
libentry-1.
|
30
|
-
libentry-1.
|
31
|
-
libentry-1.
|
25
|
+
libentry-1.30.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
26
|
+
libentry-1.30.1.dist-info/METADATA,sha256=C_q7ivxg5PW0_Qa_B8CMVc0OZUNmysfljhvmEjBHHyI,1161
|
27
|
+
libentry-1.30.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
28
|
+
libentry-1.30.1.dist-info/entry_points.txt,sha256=1v_nLVDsjvVJp9SWhl4ef2zZrsLTBtFWgrYFgqvQBgc,61
|
29
|
+
libentry-1.30.1.dist-info/top_level.txt,sha256=u2uF6-X5fn2Erf9PYXOg_6tntPqTpyT-yzUZrltEd6I,9
|
30
|
+
libentry-1.30.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
31
|
+
libentry-1.30.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|