Forráskód Böngészése

Merge branch 'master' into master_task_710395169

jackson 2 hete
szülő
commit
02b8637b61

+ 3 - 3
README.md

@@ -17,7 +17,7 @@ Gateway 保持“薄网关”边界:
 - 支持 Redis token/session 存储。
 - 支持文件 token store 作为开发排障兜底。
 - 不再支持授权码绑定工具;正式接入只使用后台生成的 `GWS_xxx` 设备配置。
-- 本地 stdio 与公网 HTTP 注册同一组 39 个查询、筛选、导出和头程文档工具。
+- 本地 stdio 与公网 HTTP 注册同一组 42 个查询、筛选、导出和头程文档工具。
 - 支持客户、订单、订单详情、轨迹、报关资料、排舱列表与详情查询。
 - 支持客户、订单与排舱筛选项,以及未排舱订单和省外进港资料导出。
 - 支持 MCP `initialize`、`tools/list`、`tools/call`、`resources/list`、`resources/read`。
@@ -25,7 +25,7 @@ Gateway 保持“薄网关”边界:
 
 ## 工具目录
 
-`GatewayApp` 与 `PublicGatewayApp` 当前注册以下 39 个候选工具:
+`GatewayApp` 与 `PublicGatewayApp` 当前注册以下 42 个候选工具:
 
 | MCP 工具 | 用途 | ThinkPHP 路由 | 最终展示 |
 |---|---|---|---|
@@ -86,7 +86,7 @@ Gateway 保持“薄网关”边界:
 
 `export_container_timeliness_report` 的筛选与查询相同:柜号、提单号可同时传入并按 AND 收窄,合计最多 200 个;没有号码时必须提供最多 31 个起运港当地日历日的干线实际出发时间闭区间。Excel 对齐后台 `OPERATE_REPORT_OUTBOUND`(序号 + 54 列)。看表格改走 `query_container_timeliness_list`。
 
-这 39 个名称只是 Gateway 的本地候选集合。员工在 `tools/list` 中实际看到、在 `tools/call` 中实际可调用的工具,始终是“Gateway 本地注册集合”与 fmsoperate 当前动态启用列表的交集;动态列表缺失、格式错误或查询失败时关闭访问,不回退为全量开放。
+这 42 个名称只是 Gateway 的本地候选集合。员工在 `tools/list` 中实际看到、在 `tools/call` 中实际可调用的工具,始终是“Gateway 本地注册集合”与 fmsoperate 当前动态启用列表的交集;动态列表缺失、格式错误或查询失败时关闭访问,不回退为全量开放。
 
 公网上传 HTTP `POST /mcp/upload-headhaul-document` 不是独立 MCP 工具;权限与启用对齐 `prepare_headhaul_document_upload`,文件字节不进入 `tools/call`。员工在对话窗口添加文件后签发,由宿主把该文件 POST 到该地址。不提供选文件框。
 

+ 1 - 1
public_gateway.py

@@ -153,7 +153,6 @@ class PublicGatewayApp:
             ),
             'export_container_timeliness_report':
                 ExportContainerTimelinessReportTool(api_client=None),
-            'query_freightower_track': QueryFreightowerTrackTool(api_client=None),
             'query_headhaul_document_list': QueryHeadhaulDocumentListTool(
                 api_client=None
             ),
@@ -167,6 +166,7 @@ class PublicGatewayApp:
             'delete_headhaul_document': DeleteHeadhaulDocumentTool(
                 api_client=None
             ),
+            'query_freightower_track': QueryFreightowerTrackTool(api_client=None),
         }
         from services.headhaul_document_app import HeadhaulSlotMemory
         self._headhaul_slots = HeadhaulSlotMemory()

+ 2 - 2
tests/test_container_timeliness_tools.py

@@ -296,8 +296,8 @@ class ContainerTimelinessToolContractTest(unittest.TestCase):
         local = GatewayApp().registered_tool_names()
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
-        self.assertEqual(38, len(OutputPresenter.SAFE_TOOLS))
+        self.assertEqual(42, len(local))
+        self.assertEqual(41, len(OutputPresenter.SAFE_TOOLS))
         self.assertIn('query_container_timeliness_list', local)
         self.assertIn('export_container_timeliness_report', local)
 

+ 1 - 1
tests/test_customer_payment_followup_tool.py

@@ -112,7 +112,7 @@ class CustomerPaymentFollowupToolTest(unittest.TestCase):
         local = GatewayApp(api_client=RecordingApiClient())
         public = PublicGatewayApp(None, None)
         self.assertEqual(local.registered_tool_names(), public.registered_tool_names())
-        self.assertEqual(39, len(local.registered_tool_names()))
+        self.assertEqual(42, len(local.registered_tool_names()))
         self.assertIn('query_customer_payment_followup', local.registered_tool_names())
 
         stdout = io.StringIO()

+ 1 - 1
tests/test_customer_payment_records_tool.py

@@ -118,7 +118,7 @@ class CustomerPaymentRecordsToolTest(unittest.TestCase):
         local = GatewayApp(api_client=client)
         public = PublicGatewayApp(None, None)
         self.assertEqual(local.registered_tool_names(), public.registered_tool_names())
-        self.assertEqual(39, len(local.registered_tool_names()))
+        self.assertEqual(42, len(local.registered_tool_names()))
         self.assertIn('query_customer_payment_records', local.registered_tool_names())
 
         stdout = io.StringIO()

+ 1 - 1
tests/test_customer_query_tools.py

@@ -68,7 +68,7 @@ class CustomerQueryToolTest(unittest.TestCase):
         local = GatewayApp().registered_tool_names()
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
+        self.assertEqual(42, len(local))
         self.assertIn('query_customer_list', local)
         self.assertIn('list_customer_filter_options', local)
 

+ 1 - 1
tests/test_customer_unverified_bill_details_tool.py

@@ -61,7 +61,7 @@ class CustomerUnverifiedBillDetailsToolTest(unittest.TestCase):
         local = GatewayApp(api_client=client)
         public = PublicGatewayApp(None, None)
         self.assertEqual(local.registered_tool_names(), public.registered_tool_names())
-        self.assertEqual(39, len(local.registered_tool_names()))
+        self.assertEqual(42, len(local.registered_tool_names()))
         self.assertIn('query_customer_unverified_bill_details', local.registered_tool_names())
 
         output = StringIO()

+ 2 - 2
tests/test_destination_trailer_tools.py

@@ -387,8 +387,8 @@ class DestinationTrailerToolContractTest(unittest.TestCase):
         local = GatewayApp().registered_tool_names()
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
-        self.assertEqual(38, len(OutputPresenter.SAFE_TOOLS))
+        self.assertEqual(42, len(local))
+        self.assertEqual(41, len(OutputPresenter.SAFE_TOOLS))
         self.assertIn('query_destination_trailer_list', local)
         self.assertIn('list_destination_trailer_filter_options', local)
 

+ 2 - 2
tests/test_export_pallet_data_tool.py

@@ -296,9 +296,9 @@ class ExportPalletDataToolTest(unittest.TestCase):
         local = GatewayApp().registered_tool_names()
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
+        self.assertEqual(42, len(local))
         self.assertIn('export_pallet_data', local)
-        self.assertEqual(38, len(OutputPresenter.SAFE_TOOLS))
+        self.assertEqual(41, len(OutputPresenter.SAFE_TOOLS))
 
     def test_presenter_reuses_queued_export_contract(self):
         presented = OutputPresenter().present(

+ 2 - 2
tests/test_export_receivable_cost_list_tool.py

@@ -168,9 +168,9 @@ class ExportReceivableCostListToolTest(unittest.TestCase):
         local = GatewayApp().registered_tool_names()
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
+        self.assertEqual(42, len(local))
         self.assertIn('export_receivable_cost_list', local)
-        self.assertEqual(38, len(OutputPresenter.SAFE_TOOLS))
+        self.assertEqual(41, len(OutputPresenter.SAFE_TOOLS))
 
     def test_presenter_reuses_queued_export_contract(self):
         presented = OutputPresenter().present(

+ 2 - 2
tests/test_headhaul_document_tools.py

@@ -161,7 +161,7 @@ class HeadhaulDocumentToolContractTest(unittest.TestCase):
         local = GatewayApp().registered_tool_names()
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
+        self.assertEqual(42, len(local))
         for name in (
             'query_headhaul_document_list',
             'list_headhaul_document_filter_options',
@@ -170,7 +170,7 @@ class HeadhaulDocumentToolContractTest(unittest.TestCase):
             'delete_headhaul_document',
         ):
             self.assertIn(name, local)
-        self.assertEqual(38, len(OutputPresenter.SAFE_TOOLS))
+        self.assertEqual(41, len(OutputPresenter.SAFE_TOOLS))
 
     def test_presenter_list_keeps_six_columns_and_document_ref(self):
         presenter = OutputPresenter()

+ 2 - 2
tests/test_order_abnormal_tools.py

@@ -341,7 +341,7 @@ class OrderAbnormalToolContractTest(unittest.TestCase):
         local = GatewayApp().registered_tool_names()
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
-        self.assertEqual(38, len(OutputPresenter.SAFE_TOOLS))
+        self.assertEqual(42, len(local))
+        self.assertEqual(41, len(OutputPresenter.SAFE_TOOLS))
         self.assertIn('query_order_abnormal_list', local)
         self.assertIn('list_order_abnormal_filter_options', local)

+ 1 - 1
tests/test_order_receivable_cost_details_tool.py

@@ -86,7 +86,7 @@ class OrderReceivableCostDetailsToolTest(unittest.TestCase):
         local = GatewayApp(api_client=client)
         public = PublicGatewayApp(None, None)
         self.assertEqual(local.registered_tool_names(), public.registered_tool_names())
-        self.assertEqual(39, len(local.registered_tool_names()))
+        self.assertEqual(42, len(local.registered_tool_names()))
         self.assertIn('query_order_receivable_cost_details', local.registered_tool_names())
         self.assertIn('query_customer_payment_records', local.registered_tool_names())
 

+ 1 - 1
tests/test_output_presenter.py

@@ -203,7 +203,7 @@ class OutputPresenterTest(unittest.TestCase):
     def test_customer_tools_are_safe_and_total_is_24(self):
         self.assertTrue(self.presenter.handles(QueryCustomerListTool.name))
         self.assertTrue(self.presenter.handles(ListCustomerFilterOptionsTool.name))
-        self.assertEqual(38, len(self.presenter.SAFE_TOOLS))
+        self.assertEqual(41, len(self.presenter.SAFE_TOOLS))
 
     def test_exact_order_uses_labels_and_drops_internal_fields(self):
         result = self.presenter.present(

+ 2 - 2
tests/test_payable_cost_tools.py

@@ -231,8 +231,8 @@ class PayableCostToolContractTest(unittest.TestCase):
         local = GatewayApp().registered_tool_names()
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
-        self.assertEqual(38, len(OutputPresenter.SAFE_TOOLS))
+        self.assertEqual(42, len(local))
+        self.assertEqual(41, len(OutputPresenter.SAFE_TOOLS))
         self.assertEqual(
             (
                 'query_payable_cost_list',

+ 1 - 1
tests/test_query_export_task_tool.py

@@ -63,7 +63,7 @@ class QueryExportTaskToolTest(unittest.TestCase):
         public = PublicGatewayApp(None, None).registered_tool_names()
 
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
+        self.assertEqual(42, len(local))
         self.assertIn('query_export_task', local)
 
     def test_cli_forwards_only_task_reference(self):

+ 2 - 2
tests/test_query_freightower_track_tool.py

@@ -96,8 +96,8 @@ class QueryFreightowerTrackToolTest(unittest.TestCase):
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
         self.assertIn('query_freightower_track', local)
-        self.assertEqual(35, len(local))
-        self.assertEqual(34, len(OutputPresenter.SAFE_TOOLS))
+        self.assertEqual(42, len(local))
+        self.assertEqual(41, len(OutputPresenter.SAFE_TOOLS))
 
     def test_cli_forwards_bill_and_container(self):
         app = GatewayApp()

+ 2 - 2
tests/test_receivable_cost_list_tools.py

@@ -279,8 +279,8 @@ class ReceivableCostIntegrationTest(unittest.TestCase):
         local = GatewayApp().registered_tool_names()
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
-        self.assertEqual(38, len(OutputPresenter.SAFE_TOOLS))
+        self.assertEqual(42, len(local))
+        self.assertEqual(41, len(OutputPresenter.SAFE_TOOLS))
         self.assertEqual(
             (
                 'query_receivable_cost_list',

+ 2 - 2
tests/test_receive_volume_tools.py

@@ -406,7 +406,7 @@ class ReceiveVolumeToolContractTest(unittest.TestCase):
         local = GatewayApp().registered_tool_names()
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
-        self.assertEqual(39, len(local))
-        self.assertEqual(38, len(OutputPresenter.SAFE_TOOLS))
+        self.assertEqual(42, len(local))
+        self.assertEqual(41, len(OutputPresenter.SAFE_TOOLS))
         self.assertIn('query_receive_volume_list', local)
         self.assertIn('list_receive_volume_filter_options', local)

+ 1 - 1
tests/test_trial_goods_tax_tool.py

@@ -56,7 +56,7 @@ class TrialGoodsTaxToolTest(unittest.TestCase):
         public = PublicGatewayApp(None, None).registered_tool_names()
         self.assertEqual(local, public)
         self.assertIn('trial_goods_tax', local)
-        self.assertEqual(36, len(local))
+        self.assertEqual(42, len(local))
         self.assertIn('trial_goods_tax', OutputPresenter.SAFE_TOOLS)
 
     def test_presenter_renders_amount(self):

+ 4 - 1
tools/trial_goods_tax.py

@@ -87,7 +87,10 @@ class TrialGoodsTaxTool:
         if not isinstance(value, str):
             raise ValueError(field + ' must be a country code')
         value = value.strip().upper()
-        if len(value) < 2 or len(value) > 3 or not value.isalpha():
+        if (
+            len(value) < 2 or len(value) > 3
+            or not value.isascii() or not value.isalpha()
+        ):
             raise ValueError(field + ' must be a country code')
         return value