libentry 1.29.1__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 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, TextContent, TextResourceContents, Tool, ToolSchema, \
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
- error = json.dumps(SubroutineError.from_exception(e))
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
- if response.error is not None:
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
- if response.error is not None:
695
- text = json.dumps(response.error)
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
- text = json.dumps(SubroutineError.from_exception(e))
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
- response = self.response_class(body, status=200, mimetype=mimetype)
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
- response = self.response_class(body, status=500, mimetype=mimetype)
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
- return FlaskServer(service, self.options)
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="服务实例名称,会在进程命令行中显示。",
@@ -1129,6 +1099,7 @@ def run_service(
1129
1099
  "ssl_context": ssl_context,
1130
1100
  "access_control_allow_origin": run_config.access_control_allow_origin,
1131
1101
  "access_control_allow_methods": run_config.access_control_allow_methods,
1102
+ "access_control_allow_headers": run_config.access_control_allow_headers,
1132
1103
  "proc_name": run_config.name,
1133
1104
  }
1134
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.29.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=DTNNnpkt91_RWloQa3Tn-EC21NlrqsRI0MpkPzs0naM,42258
17
- libentry/mcp/types.py,sha256=aAoVO4jjqEvDzNneuZapmRYonLLnGsbcLoypVyRNNYg,12389
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.29.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
26
- libentry-1.29.1.dist-info/METADATA,sha256=ZtsgWLuz0EkgSZ35HWtPX5ocy7Lvuhzjo6QaOOOE5Qo,1135
27
- libentry-1.29.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
28
- libentry-1.29.1.dist-info/entry_points.txt,sha256=1v_nLVDsjvVJp9SWhl4ef2zZrsLTBtFWgrYFgqvQBgc,61
29
- libentry-1.29.1.dist-info/top_level.txt,sha256=u2uF6-X5fn2Erf9PYXOg_6tntPqTpyT-yzUZrltEd6I,9
30
- libentry-1.29.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
31
- libentry-1.29.1.dist-info/RECORD,,
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,,