class QueryCustomsDeclarationFilesTool: name = 'query_customs_declaration_files' route_path = '/mcp/tools/queryCustomsDeclarationFiles' MAX_NUMBERS = 100 def __init__(self, api_client=None): self.api_client = api_client def metadata(self): number_item = { 'type': 'string', 'minLength': 1, 'maxLength': 100, } return { 'name': self.name, 'description': ( '批量查询订单的报关资料文件链接。用户明确说“订单号”时使用' '订单号数组;用户明确说“排舱单号”时使用排舱单号数组。' '这里的订单号是后台订单列表及排舱详情“订单号”列显示的号码,' '不是系统单号、数据库 order_id/id、客户参考号、快递单号或排舱单号。' '一次调用只能选择一种号码,单个号码也必须放入数组。' '如果用户没有明确说明是订单号还是排舱单号,必须先提问,' '让用户选择“订单号”或“排舱单号”,确认前不得调用工具。' '用户只说“单号”时必须先追问是订单号还是排舱单号。' '用户说“系统单号”时也不得当作订单号;必须先确认并取得订单号。' '不得根据号码格式猜测。' '查询无结果后不得切换号码类型,不得跨字段重试。' '参数名仅用于工具调用;向用户回答时只能使用中文业务名称,' '不得展示内部参数名。' ), 'input_schema': { 'type': 'object', 'properties': { 'outbound_numbers': { 'type': 'array', 'items': dict(number_item), 'minItems': 1, 'maxItems': self.MAX_NUMBERS, 'description': ( '多个排舱单号。仅当用户明确说“排舱单号”时使用;' '不是订单号、客户参考号或快递单号。' ), 'examples': [['PC202607150001', 'PC202607150002']], }, 'order_numbers': { 'type': 'array', 'items': dict(number_item), 'minItems': 1, 'maxItems': self.MAX_NUMBERS, 'description': ( '多个订单号,对应后台订单列表及排舱详情“订单号”列的值。' '仅当用户明确说“订单号”时使用;不是系统单号或数据库' ' order_id/id,也不是客户参考号、快递单号或排舱单号。' '用户提供“系统单号”时不得填入此字段,必须先追问取得订单号。' ), 'examples': [['ORD202607150001', 'ORD202607150002']], }, 'page': { 'type': 'integer', 'minimum': 1, 'maximum': 100, 'default': 1, 'description': '页码,从 1 开始,最大 100。', }, 'limit': { 'type': 'integer', 'minimum': 1, 'maximum': 100, 'default': 20, 'description': '每页订单数量,范围 1 到 100。', }, }, 'oneOf': [ {'required': ['outbound_numbers']}, {'required': ['order_numbers']}, ], 'additionalProperties': False, }, } def call( self, outbound_numbers=None, order_numbers=None, page=1, limit=20, request_id='rq_query_customs_declaration_files', ): if self.api_client is None: raise RuntimeError( 'api client is required for query_customs_declaration_files' ) has_outbound = outbound_numbers is not None has_order = order_numbers is not None if has_outbound == has_order: raise ValueError( 'provide outbound_numbers or order_numbers' ) field = 'outbound_numbers' if has_outbound else 'order_numbers' values = outbound_numbers if has_outbound else order_numbers numbers = self._normalize_numbers(values, field) page = self._bounded_integer(page, 'page', 100) limit = self._bounded_integer(limit, 'limit', 100) return self.api_client.call_tool( self.name, self.route_path, { field: numbers, 'page': page, 'limit': limit, }, request_id, ) @classmethod def _normalize_numbers(cls, values, field): if not isinstance(values, list): raise ValueError('{0} must be an array'.format(field)) if len(values) > cls.MAX_NUMBERS: raise ValueError('{0} must contain 1 to 100 values'.format(field)) numbers = [] for value in values: if not isinstance(value, str): raise ValueError('{0} must contain strings'.format(field)) value = value.strip() if not value: continue if len(value) > 100: raise ValueError('{0} contains an invalid number'.format(field)) if value not in numbers: numbers.append(value) if not numbers: raise ValueError('{0} must contain 1 to 100 values'.format(field)) return numbers @staticmethod def _bounded_integer(value, field, maximum): if isinstance(value, bool): raise ValueError('{0} must be between 1 and {1}'.format(field, maximum)) try: value = int(value) except (TypeError, ValueError): raise ValueError('{0} must be between 1 and {1}'.format(field, maximum)) if value < 1 or value > maximum: raise ValueError('{0} must be between 1 and {1}'.format(field, maximum)) return value