cmdbox 0.6.4__py3-none-any.whl → 0.6.4.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.

Potentially problematic release.


This version of cmdbox might be problematic. Click here for more details.

Files changed (74) hide show
  1. cmdbox/app/app.py +6 -0
  2. cmdbox/app/common.py +7 -4
  3. cmdbox/app/commons/loghandler.py +101 -20
  4. cmdbox/app/edge.py +1 -3
  5. cmdbox/app/features/cli/cmdbox_agent_mcp_client.py +175 -0
  6. cmdbox/app/features/cli/cmdbox_agent_mcp_proxy.py +96 -0
  7. cmdbox/app/features/cli/cmdbox_cmd_list.py +1 -1
  8. cmdbox/app/features/cli/cmdbox_server_list.py +1 -1
  9. cmdbox/app/features/cli/cmdbox_server_start.py +2 -2
  10. cmdbox/app/features/cli/cmdbox_server_stop.py +2 -2
  11. cmdbox/app/features/cli/cmdbox_tts_install.py +11 -1
  12. cmdbox/app/features/cli/cmdbox_tts_start.py +1 -1
  13. cmdbox/app/features/cli/cmdbox_web_apikey_add.py +1 -1
  14. cmdbox/app/features/cli/cmdbox_web_apikey_del.py +1 -1
  15. cmdbox/app/features/cli/cmdbox_web_gencert.py +1 -1
  16. cmdbox/app/features/cli/cmdbox_web_genpass.py +1 -1
  17. cmdbox/app/features/cli/cmdbox_web_group_add.py +1 -1
  18. cmdbox/app/features/cli/cmdbox_web_group_del.py +1 -1
  19. cmdbox/app/features/cli/cmdbox_web_group_edit.py +1 -1
  20. cmdbox/app/features/cli/cmdbox_web_group_list.py +2 -2
  21. cmdbox/app/features/cli/cmdbox_web_start.py +1 -0
  22. cmdbox/app/features/cli/cmdbox_web_stop.py +1 -1
  23. cmdbox/app/features/cli/cmdbox_web_user_add.py +1 -1
  24. cmdbox/app/features/cli/cmdbox_web_user_del.py +1 -1
  25. cmdbox/app/features/cli/cmdbox_web_user_edit.py +1 -1
  26. cmdbox/app/features/cli/cmdbox_web_user_list.py +2 -2
  27. cmdbox/app/features/web/cmdbox_web_agent.py +16 -4
  28. cmdbox/app/features/web/cmdbox_web_get_cmd_choices.py +10 -2
  29. cmdbox/app/features/web/cmdbox_web_save_cmd.py +1 -0
  30. cmdbox/app/features/web/cmdbox_web_versions_used.py +4 -0
  31. cmdbox/app/mcp.py +4 -1
  32. cmdbox/app/options.py +6 -4
  33. cmdbox/app/web.py +39 -17
  34. cmdbox/extensions/features.yml +4 -1
  35. cmdbox/licenses/LICENSE_APScheduler_3_11_0_MIT_License.txt +19 -0
  36. cmdbox/licenses/LICENSE_SQLAlchemy_2_0_43_MIT.txt +19 -0
  37. cmdbox/licenses/LICENSE_anyio_4_10_0_UNKNOWN.txt +20 -0
  38. cmdbox/licenses/LICENSE_backoff_2_2_1_MIT_License.txt +21 -0
  39. cmdbox/licenses/LICENSE_certifi_2025_8_3_Mozilla_Public_License_2_0-MPL_2_0.txt +20 -0
  40. cmdbox/licenses/LICENSE_charset-normalizer_3_4_3_MIT.txt +21 -0
  41. cmdbox/licenses/LICENSE_cryptography_45_0_6_Apache-2_0_OR_BSD-3-Clause.txt +3 -0
  42. cmdbox/licenses/LICENSE_fastapi-sso_0_18_0_MIT_License.txt +21 -0
  43. cmdbox/licenses/LICENSE_fastmcp_2_11_3_Apache_Software_License.txt +201 -0
  44. cmdbox/licenses/LICENSE_google-adk_1_10_0_Apache_Software_License.txt +202 -0
  45. cmdbox/licenses/LICENSE_google-api-python-client_2_178_0_Apache_Software_License.txt +201 -0
  46. cmdbox/licenses/LICENSE_google-cloud-aiplatform_1_108_0_Apache_2_0.txt +202 -0
  47. cmdbox/licenses/LICENSE_google-genai_1_29_0_Apache_Software_License.txt +202 -0
  48. cmdbox/licenses/LICENSE_greenlet_3_2_4_MIT_AND_Python-2_0.txt +30 -0
  49. cmdbox/licenses/LICENSE_huggingface-hub_0_34_4_Apache_Software_License.txt +201 -0
  50. cmdbox/licenses/LICENSE_litellm-enterprise_0_1_19_UNKNOWN.txt +37 -0
  51. cmdbox/licenses/LICENSE_litellm_1_75_5_post1_MIT_License.txt +26 -0
  52. cmdbox/licenses/LICENSE_markdown-it-py_4_0_0_MIT_License.txt +21 -0
  53. cmdbox/licenses/LICENSE_mcp_1_12_4_MIT_License.txt +21 -0
  54. cmdbox/licenses/LICENSE_multidict_6_6_4_Apache_License_2_0.txt +13 -0
  55. cmdbox/licenses/LICENSE_oauthlib_3_3_1_BSD-3-Clause.txt +27 -0
  56. cmdbox/licenses/LICENSE_openai_1_99_9_Apache_Software_License.txt +201 -0
  57. cmdbox/licenses/LICENSE_orjson_3_11_1_Apache_Software_License-MIT_License.txt +201 -0
  58. cmdbox/licenses/LICENSE_redis_6_4_0_MIT_License.txt +21 -0
  59. cmdbox/licenses/LICENSE_rpds-py_0_27_0_UNKNOWN.txt +19 -0
  60. cmdbox/licenses/LICENSE_sphinx-intl_2_3_2_UNKNOWN.txt +25 -0
  61. cmdbox/licenses/LICENSE_tenacity_9_1_2_Apache_Software_License.txt +202 -0
  62. cmdbox/licenses/LICENSE_tiktoken_0_11_0_MIT_License-Copyright-c-2022_OpenAI-Shantanu_Jain-Permission_is_hereby_granted-free_of_charge-to_any_per.txt +21 -0
  63. cmdbox/licenses/files.txt +28 -23
  64. cmdbox/logconf_cmdbox.yml +32 -0
  65. cmdbox/version.py +2 -2
  66. cmdbox/web/agent.html +8 -0
  67. cmdbox/web/assets/cmdbox/agent.js +45 -1
  68. cmdbox/web/assets/cmdbox/common.js +4 -22
  69. {cmdbox-0.6.4.dist-info → cmdbox-0.6.4.1.dist-info}/METADATA +2 -1
  70. {cmdbox-0.6.4.dist-info → cmdbox-0.6.4.1.dist-info}/RECORD +74 -44
  71. {cmdbox-0.6.4.dist-info → cmdbox-0.6.4.1.dist-info}/WHEEL +0 -0
  72. {cmdbox-0.6.4.dist-info → cmdbox-0.6.4.1.dist-info}/entry_points.txt +0 -0
  73. {cmdbox-0.6.4.dist-info → cmdbox-0.6.4.1.dist-info}/licenses/LICENSE +0 -0
  74. {cmdbox-0.6.4.dist-info → cmdbox-0.6.4.1.dist-info}/top_level.txt +0 -0
cmdbox/app/app.py CHANGED
@@ -6,6 +6,7 @@ import argparse
6
6
  import argcomplete
7
7
  import logging
8
8
  import time
9
+ import threading
9
10
  import sys
10
11
 
11
12
 
@@ -180,4 +181,9 @@ class CmdBoxApp:
180
181
  アプリケーションの設定を読み込みます。
181
182
  """
182
183
  logger, _ = common.load_config(args.mode, debug=args.debug, data=args.data, webcall=webcall if args.cmd != 'webcap' else True, ver=self.ver)
184
+ if not hasattr(common, 'logsv') and hasattr(args, 'logsv') and args.logsv:
185
+ from cmdbox.app.commons import loghandler
186
+ common.logsv = loghandler.LogRecordTCPServer("logsv", host="localhost", port=9020, debug=args.debug)
187
+ threading.Thread(daemon=True, target=common.logsv.serve_until_stopped, name="logsv").start()
188
+
183
189
  return logger
cmdbox/app/common.py CHANGED
@@ -4,7 +4,6 @@ from cmdbox.app.commons import convert, module, loghandler
4
4
  from cryptography.fernet import Fernet
5
5
  from pathlib import Path
6
6
  from rich.console import Console
7
- from rich.logging import RichHandler
8
7
  from tabulate import tabulate
9
8
  from typing import List, Tuple, Dict, Any
10
9
  import argparse
@@ -195,7 +194,7 @@ def console_log(console:Console, message:Any, highlight:bool=True, **kwargs) ->
195
194
  **kwargs: その他のキーワード引数
196
195
  """
197
196
  dtstr = datetime.datetime.now().strftime('[%Y-%m-%d %H:%M:%S]')
198
- console.print(f"{dtstr} {message}", highlight=highlight, **kwargs)
197
+ console.print(f"{dtstr} {message}", highlight=highlight, crop=False, soft_wrap=True, **kwargs)
199
198
 
200
199
  def default_logger(debug:bool=False, ver=version, webcall:bool=False) -> logging.Logger:
201
200
  """
@@ -268,8 +267,12 @@ def load_config(mode:str, debug:bool=False, data=HOME_DIR, webcall:bool=False, v
268
267
  for k, l in log_config['loggers'].items():
269
268
  if 'handlers' in l and std_key in l['handlers']:
270
269
  l['handlers'].remove(std_key)
271
- if 'loggers' not in log_config or log_name not in log_config['loggers']:
272
- raise BaseException(f"Loggers not found.({log_name}) at log_conf_path={log_conf_path}")
270
+ if 'loggers' not in log_config:
271
+ raise BaseException(f"Loggers not found at log_conf_path={log_conf_path}")
272
+ if log_name not in log_config['loggers']:
273
+ if ver.__appid__ not in log_config['loggers']:
274
+ raise BaseException(f"Loggers not found.({ver.__appid__}) at log_conf_path={log_conf_path}")
275
+ log_name = ver.__appid__
273
276
  log_config['disable_existing_loggers'] = False # これを入れないとdictConfigで既存のロガーが無効になる
274
277
  logging.config.dictConfig(log_config)
275
278
  logger = logging.getLogger(log_name)
@@ -4,6 +4,11 @@ from rich.theme import Theme
4
4
  import re
5
5
  import logging
6
6
  import logging.handlers
7
+ import pickle
8
+ import socketserver
9
+ import struct
10
+ import socket
11
+
7
12
 
8
13
  class Colors:
9
14
  S = "\033["
@@ -103,6 +108,9 @@ theme=Theme({
103
108
  "repr.log_success": "green",})
104
109
 
105
110
  class LogLevelHighlighter(highlighter.ReprHighlighter):
111
+ """
112
+ ログメッセージのログレベルをハイライトします。
113
+ """
106
114
  def __init__(self):
107
115
  #self.highlights = []
108
116
  self.highlights.append(r"(?P<log_debug>DEBUG|EXEC)")
@@ -112,29 +120,12 @@ class LogLevelHighlighter(highlighter.ReprHighlighter):
112
120
  self.highlights.append(r"(?P<log_fatal>FATAL|CRITICAL)")
113
121
  self.highlights.append(r"(?P<log_product>CMDBOX|IINFER|USOUND|GAIAN|GAIC|WITSHAPE)")
114
122
  self.highlights.append(r"(?P<log_success>SUCCESS|OK|PASSED|DONE|COMPLETE|START|FINISH|OPEN|CONNECTED|ALLOW)")
115
- """
116
- self.highlights.append(r"(?P<tag_start><)(?P<tag_name>[-\w.:|]*)(?P<tag_contents>[\w\W]*)(?P<tag_end>>)")
117
- self.highlights.append(r'(?P<attrib_name>[\w_]{1,50})=(?P<attrib_value>"?[\w_]+"?)?')
118
- self.highlights.append(r"(?P<brace>[][{}()])")
119
- self.highlights.append(highlighter._combine_regex(
120
- r"(?P<ipv4>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})",
121
- r"(?P<ipv6>([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})",
122
- r"(?P<eui64>(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})",
123
- r"(?P<eui48>(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})",
124
- r"(?P<uuid>[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})",
125
- r"(?P<call>[\w.]*?)\(",
126
- r"\b(?P<bool_true>True)\b|\b(?P<bool_false>False)\b|\b(?P<none>None)\b",
127
- r"(?P<ellipsis>\.\.\.)",
128
- r"(?P<number_complex>(?<!\w)(?:\-?[0-9]+\.?[0-9]*(?:e[-+]?\d+?)?)(?:[-+](?:[0-9]+\.?[0-9]*(?:e[-+]?\d+)?))?j)",
129
- r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[-+]?\d+?)?\b|0x[0-9a-fA-F]*)",
130
- r"(?P<path>\B(/[-\w._+]+)*\/)(?P<filename>[-\w._+]*)?",
131
- r"(?<![\\\w])(?P<str>b?'''.*?(?<!\\)'''|b?'.*?(?<!\\)'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
132
- r"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)",
133
- ))
134
- """
135
123
  self.highlights = [re.compile(h, re.IGNORECASE) for h in self.highlights]
136
124
 
137
125
  class ColorfulStreamHandler(logging.StreamHandler):
126
+ """
127
+ コンソールにカラフルなログメッセージを出力します。
128
+ """
138
129
  console = Console(soft_wrap=True, height=True, highlighter=LogLevelHighlighter(), theme=theme)
139
130
 
140
131
  def emit(self, record: logging.LogRecord) -> None:
@@ -154,3 +145,93 @@ class TimedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler):
154
145
  record.levelname = level_mapping_nc[record.levelno]
155
146
  super().emit(record)
156
147
 
148
+ class SocketHandler(logging.handlers.SocketHandler):
149
+ def emit(self, record: logging.LogRecord) -> None:
150
+ record.levelname = level_mapping_nc[record.levelno]
151
+ super().emit(record)
152
+
153
+ class LogRecordRequestHandler(socketserver.StreamRequestHandler):
154
+ """
155
+ ログリクエストを処理するためのハンドラ
156
+ """
157
+ def setup(self):
158
+ super().setup()
159
+ from cmdbox.app import common
160
+ common.set_debug(self._getLogger(), LogRecordTCPServer.debug)
161
+
162
+ def _getLogger(self):
163
+ if self.server.logname is not None:
164
+ name = self.server.logname
165
+ else:
166
+ name = self.logname
167
+ return logging.getLogger(name)
168
+
169
+ def handle(self):
170
+ """
171
+ ログリクエストを処理します。
172
+ """
173
+ while True:
174
+ chunk = self.connection.recv(4)
175
+ if len(chunk) < 4:
176
+ break
177
+ slen = struct.unpack('>L', chunk)[0]
178
+ chunk = self.connection.recv(slen)
179
+ while len(chunk) < slen:
180
+ chunk = chunk + self.connection.recv(slen - len(chunk))
181
+ obj = self.unPickle(chunk)
182
+ record = logging.makeLogRecord(obj)
183
+ self.handleLogRecord(record)
184
+
185
+ def unPickle(self, data):
186
+ return pickle.loads(data)
187
+
188
+ def handleLogRecord(self, record):
189
+ logger = self._getLogger()
190
+ logger.handle(record)
191
+
192
+ class LogRecordTCPServer(socketserver.ThreadingTCPServer):
193
+ """
194
+ ログレコードを受信するためのTCPサーバー。
195
+ """
196
+ # 停止後すぐにサーバーを再起動できるようにする
197
+ allow_reuse_address = True
198
+
199
+ def __init__(self, logname, host='localhost', port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
200
+ handler=LogRecordRequestHandler, debug=False):
201
+ """
202
+ コンストラクタ
203
+
204
+ Args:
205
+ logname (str): ログ名
206
+ host (str): ホスト名
207
+ port (int): ポート番号
208
+ handler (socketserver.RequestHandler): リクエストハンドラ
209
+ debug (bool): デバッグモード
210
+ """
211
+ socketserver.ThreadingTCPServer.__init__(self, (host, port), handler, bind_and_activate=False)
212
+ self.allow_reuse_address = False
213
+ self.allow_reuse_port = False
214
+ self.request_queue_size = 15
215
+ self.abort = 0
216
+ self.timeout = 1
217
+ self.logname = logname
218
+ self.handler = handler
219
+ LogRecordTCPServer.debug = debug
220
+
221
+ def serve_until_stopped(self):
222
+ import select
223
+ abort = 0
224
+ try:
225
+ self.server_bind()
226
+ self.server_activate()
227
+ except:
228
+ # すでにlogsvが起動中の場合は待機しない
229
+ self.server_close()
230
+ abort = 1
231
+ while not abort:
232
+ rd, wr, ex = select.select([self.socket.fileno()],
233
+ [], [],
234
+ self.timeout)
235
+ if rd:
236
+ self.handle_request()
237
+ abort = self.abort
cmdbox/app/edge.py CHANGED
@@ -9,7 +9,6 @@ from uvicorn.config import Config
9
9
  import argparse
10
10
  import json
11
11
  import logging
12
- import locale
13
12
  import queue
14
13
  import requests
15
14
  import time
@@ -54,7 +53,6 @@ class Edge(object):
54
53
 
55
54
  import questionary
56
55
  ref_opts = self.options.get_cmd_choices(edge_mode, edge_cmd)
57
- language, _ = locale.getlocale()
58
56
  edge_dir = Path(self.data) / '.edge'
59
57
  common.mkdirs(edge_dir)
60
58
  conf_file = edge_dir / 'edge.conf'
@@ -96,7 +94,7 @@ class Edge(object):
96
94
  default = str(default) if isinstance(default, int) or isinstance(default, float) else default
97
95
  description_ja = r['description_ja'] if 'description_ja' in r else None
98
96
  description_en = r['description_en'] if 'description_en' in r else None
99
- help = description_en if language.find('Japan') < 0 and language.find('ja_JP') < 0 else description_ja
97
+ help = description_en if not common.is_japan() else description_ja
100
98
  choice = r['choice'] if 'choice' in r else None
101
99
  choice = [str(c) for c in choice] if choice is not None else None
102
100
  required = r['required'] if 'required' in r else False
@@ -0,0 +1,175 @@
1
+ from cmdbox.app import common, feature
2
+ from cmdbox.app.features.cli import cmdbox_web_start
3
+ from cmdbox.app.options import Options
4
+ from typing import Dict, Any, Tuple, List, Union
5
+ import argparse
6
+ import logging
7
+
8
+
9
+ class AgentMcpClient(feature.UnsupportEdgeFeature):
10
+ def get_mode(self) -> Union[str, List[str]]:
11
+ """
12
+ この機能のモードを返します
13
+
14
+ Returns:
15
+ Union[str, List[str]]: モード
16
+ """
17
+ return 'agent'
18
+
19
+ def get_cmd(self) -> str:
20
+ """
21
+ この機能のコマンドを返します
22
+
23
+ Returns:
24
+ str: コマンド
25
+ """
26
+ return 'mcp_client'
27
+
28
+ def get_option(self):
29
+ """
30
+ この機能のオプションを返します
31
+
32
+ Returns:
33
+ Dict[str, Any]: オプション
34
+ """
35
+ return dict(
36
+ # webからclientを実行するとmcp処理とデッドロックが発生するため、webmodeを無効にします。
37
+ use_redis=self.USE_REDIS_FALSE, nouse_webmode=True, use_agent=False,
38
+ description_ja="リモートMCPサーバーにリクエストを行うMCPクライアントを起動します。",
39
+ description_en="Starts an MCP client that makes requests to a remote MCP server.",
40
+ choice=[
41
+ dict(opt="mcpserver_name", type=Options.T_STR, default='mcpserver', required=True, multi=False, hide=False, choice=None,
42
+ description_ja="リモートMCPサーバーの名前を指定します。省略した場合は`mcpserver`となります。",
43
+ description_en="Specify the name of the MCP server. If omitted, it will be `mcpserver`.",),
44
+ dict(opt="mcpserver_url", type=Options.T_STR, default='http://localhost:8081/mcpsv/mcp', required=True, multi=False, hide=False, choice=None,
45
+ description_ja="リモートMCPサーバーのURLを指定します。省略した場合は`http://localhost:8081/mcpsv/mcp`となります。",
46
+ description_en="Specifies the URL of the remote MCP server. If omitted, it will be `http://localhost:8081/mcpsv/mcp`.",),
47
+ dict(opt="mcpserver_apikey", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
48
+ description_ja="リモートMCPサーバーのAPI Keyを指定します。",
49
+ description_en="Specify the API Key of the remote MCP server.",),
50
+ dict(opt="mcpserver_transport", type=Options.T_STR, default='streamable-http', required=True, multi=False, hide=False, choice=['', 'streamable-http', 'sse', 'http'],
51
+ description_ja="リモートMCPサーバーのトランスポートを指定します。省略した場合は`streamable-http`となります。",
52
+ description_en="Specifies the transport of the remote MCP server. If omitted, it is `streamable-http`.",),
53
+ dict(opt="operation", type=Options.T_STR, default='list_tools', required=True, multi=False, hide=False,
54
+ choice=['list_tools', 'call_tool', 'list_resources', 'read_resource', 'list_prompts', 'get_prompt'],
55
+ choice_show=dict(call_tool=['tool_name', 'tool_args', 'mcp_timeout',],
56
+ read_resource=['resource_url',],
57
+ get_prompt=['prompt_name', 'prompt_args']),
58
+ description_ja="リモートMCPサーバーに要求する操作を指定します。省略した場合は`list_tools`となります。",
59
+ description_en="Specifies the operations to request from the remote MCP server. If omitted, `list_tools` is used.",),
60
+ dict(opt="tool_name", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
61
+ description_ja="リモートMCPサーバーで実行するツールの名前を指定します。",
62
+ description_en="Specify the name of the tool to run on the remote MCP server."),
63
+ dict(opt="tool_args", type=Options.T_DICT, default=None, required=False, multi=True, hide=False, choice=None,
64
+ description_ja="リモートMCPサーバーで実行するツールの引数を指定します。",
65
+ description_en="Specify arguments for the tool to run on the remote MCP server."),
66
+ dict(opt="mcp_timeout", type=Options.T_INT, default="60", required=False, multi=False, hide=False, choice=None,
67
+ description_ja="リモートMCPサーバーの応答が返ってくるまでの最大待ち時間を指定します。",
68
+ description_en="Specifies the maximum time to wait for a response from the remote MCP server."),
69
+ dict(opt="resource_url", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
70
+ description_ja="リモートMCPサーバーから取得するリソースのURLを指定します。",
71
+ description_en="Specify the URL of the resource to retrieve from the remote MCP server."),
72
+ dict(opt="prompt_name", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
73
+ description_ja="リモートMCPサーバーから取得するプロンプトの名前を指定します。",
74
+ description_en="Specifies the name of the prompt to be retrieved from the remote MCP server."),
75
+ dict(opt="prompt_args", type=Options.T_DICT, default=None, required=False, multi=True, hide=False, choice=None,
76
+ description_ja="リモートMCPサーバーから取得するプロンプトの引数を指定します。",
77
+ description_en="Specifies prompt arguments to be retrieved from the remote MCP server."),
78
+ dict(opt="output_json", short="o", type=Options.T_FILE, default=None, required=False, multi=False, hide=True, choice=None, fileio="out",
79
+ description_ja="処理結果jsonの保存先ファイルを指定。",
80
+ description_en="Specify the destination file for saving the processing result json."),
81
+ dict(opt="output_json_append", short="a", type=Options.T_BOOL, default=False, required=False, multi=False, hide=True, choice=[True, False],
82
+ description_ja="処理結果jsonファイルを追記保存します。",
83
+ description_en="Save the processing result json file by appending."),
84
+ dict(opt="stdout_log", type=Options.T_BOOL, default=True, required=False, multi=False, hide=True, choice=[True, False],
85
+ description_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力をConsole logに出力します。",
86
+ description_en="Available only in GUI mode. Outputs standard output during command execution to Console log."),
87
+ dict(opt="capture_stdout", type=Options.T_BOOL, default=True, required=False, multi=False, hide=True, choice=[True, False],
88
+ description_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力をキャプチャーし、実行結果画面に表示します。",
89
+ description_en="Available only in GUI mode. Captures standard output during command execution and displays it on the execution result screen."),
90
+ dict(opt="capture_maxsize", type=Options.T_INT, default=self.DEFAULT_CAPTURE_MAXSIZE, required=False, multi=False, hide=True, choice=None,
91
+ description_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力の最大キャプチャーサイズを指定します。",
92
+ description_en="Available only in GUI mode. Specifies the maximum capture size of standard output when executing commands."),
93
+ ])
94
+
95
+ async def apprun(self, logger:logging.Logger, args:argparse.Namespace, tm:float, pf:List[Dict[str, float]]=[]) -> Tuple[int, Dict[str, Any], Any]:
96
+ """
97
+ この機能の実行を行います
98
+
99
+ Args:
100
+ logger (logging.Logger): ロガー
101
+ args (argparse.Namespace): 引数
102
+ tm (float): 実行開始時間
103
+ pf (List[Dict[str, float]]): 呼出元のパフォーマンス情報
104
+
105
+ Returns:
106
+ Tuple[int, Dict[str, Any], Any]: 終了コード, 結果, オブジェクト
107
+ """
108
+ logger.info("apprun function has started.")
109
+ if not hasattr(args, 'mcpserver_name'):
110
+ args.mcpserver_name = 'mcpserver'
111
+ if not hasattr(args, 'mcpserver_url'):
112
+ args.mcpserver_url = 'http://localhost:8081/mcpsv/mcp'
113
+ if not hasattr(args, 'mcpserver_transport'):
114
+ args.mcpserver_transport = 'streamable-http'
115
+ if not hasattr(args, 'mcpserver_apikey'):
116
+ args.mcpserver_apikey = None
117
+
118
+ from fastmcp import Client
119
+ config = dict(
120
+ mcpServers=dict(
121
+ default=dict(
122
+ url=args.mcpserver_url,
123
+ transport=args.mcpserver_transport,
124
+ auth=args.mcpserver_apikey
125
+ )
126
+ )
127
+ )
128
+ try:
129
+ common.reset_logger('FastMCP.fastmcp.server.server')
130
+ client = Client(config)
131
+ if logger.level == logging.DEBUG:
132
+ logger.debug(f"Starting MCP client: {config}")
133
+ async with client:
134
+ if args.operation == 'list_tools':
135
+ result = await client.list_tools()
136
+ ret = dict(success=[r.__dict__ for r in result])
137
+ elif args.operation == 'call_tool':
138
+ if not args.tool_name:
139
+ raise ValueError("Tool name must be specified for 'call_tool' operation.")
140
+ if not args.tool_args:
141
+ args.tool_args = dict()
142
+ if not hasattr(args, 'mcp_timeout'):
143
+ args.mcp_timeout = 60
144
+ result = await client.call_tool(args.tool_name, arguments=args.tool_args, timeout=args.mcp_timeout)
145
+ ret = dict(success=result.__dict__)
146
+ elif args.operation == 'list_resources':
147
+ result = await client.list_resources()
148
+ ret = dict(success=[r.__dict__ for r in result])
149
+ elif args.operation == 'read_resource':
150
+ if not args.resource_url:
151
+ raise ValueError("Resource URL must be specified for 'read_resource' operation.")
152
+ result = await client.read_resource(args.resource_url)
153
+ ret = dict(success=result.__dict__)
154
+ elif args.operation == 'list_prompts':
155
+ result = await client.list_prompts()
156
+ ret = dict(success=[r.__dict__ for r in result])
157
+ elif args.operation == 'get_prompt':
158
+ if not args.prompt_name:
159
+ raise ValueError("Prompt name must be specified for 'get_prompt' operation.")
160
+ if not args.prompt_args:
161
+ args.prompt_args = dict()
162
+ result = await client.get_prompt(args.prompt_name, arguments=args.prompt_args)
163
+ ret = dict(success=result.__dict__)
164
+ else:
165
+ raise ValueError(f"Unknown operation: {args.operation}")
166
+ common.print_format(ret, args.format, tm, args.output_json, args.output_json_append, pf=pf)
167
+ return self.RESP_SUCCESS, ret, None
168
+ except Exception as e:
169
+ logger.setLevel(logging.ERROR)
170
+ for h in logger.handlers:
171
+ h.setLevel(logging.ERROR)
172
+ logger.error(f"Failed to start MCP proxy: {e}", exc_info=True)
173
+ msg = dict(warn=f"Failed to start MCP proxy: {e}")
174
+ common.print_format(msg, args.format, tm, args.output_json, args.output_json_append, pf=pf)
175
+ return self.RESP_ERROR, msg, None
@@ -0,0 +1,96 @@
1
+ from cmdbox.app import common, feature
2
+ from cmdbox.app.features.cli import cmdbox_web_start
3
+ from cmdbox.app.options import Options
4
+ from typing import Dict, Any, Tuple, List, Union
5
+ import argparse
6
+ import logging
7
+
8
+
9
+ class AgentMcpProxy(feature.UnsupportEdgeFeature):
10
+ def get_mode(self) -> Union[str, List[str]]:
11
+ """
12
+ この機能のモードを返します
13
+
14
+ Returns:
15
+ Union[str, List[str]]: モード
16
+ """
17
+ return 'agent'
18
+
19
+ def get_cmd(self) -> str:
20
+ """
21
+ この機能のコマンドを返します
22
+
23
+ Returns:
24
+ str: コマンド
25
+ """
26
+ return 'mcp_proxy'
27
+
28
+ def get_option(self):
29
+ """
30
+ この機能のオプションを返します
31
+
32
+ Returns:
33
+ Dict[str, Any]: オプション
34
+ """
35
+ return dict(
36
+ use_redis=self.USE_REDIS_FALSE, nouse_webmode=True, use_agent=False,
37
+ description_ja="標準入力を受け付け、リモートMCPサーバーにリクエストを行うProxyサーバーを起動します。",
38
+ description_en="Starts a Proxy server that accepts standard input and makes requests to a remote MCP server.",
39
+ choice=[
40
+ dict(opt="mcpserver_name", type=Options.T_STR, default='mcpserver', required=True, multi=False, hide=False, choice=None,
41
+ description_ja="リモートMCPサーバーの名前を指定します。省略した場合は`mcpserver`となります。",
42
+ description_en="Specify the name of the MCP server. If omitted, it will be `mcpserver`.",),
43
+ dict(opt="mcpserver_url", type=Options.T_STR, default='http://localhost:8081/mcpsv/mcp', required=True, multi=False, hide=False, choice=None,
44
+ description_ja="リモートMCPサーバーのURLを指定します。省略した場合は`http://localhost:8081/mcpsv/mcp`となります。",
45
+ description_en="Specifies the URL of the remote MCP server. If omitted, it will be `http://localhost:8081/mcpsv/mcp`.",),
46
+ dict(opt="mcpserver_apikey", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
47
+ description_ja="リモートMCPサーバーのAPI Keyを指定します。",
48
+ description_en="Specify the API Key of the remote MCP server.",),
49
+ dict(opt="mcpserver_transport", type=Options.T_STR, default='streamable-http', required=True, multi=False, hide=False, choice=['', 'streamable-http', 'sse', 'http'],
50
+ description_ja="リモートMCPサーバーのトランスポートを指定します。省略した場合は`streamable-http`となります。",
51
+ description_en="Specifies the transport of the remote MCP server. If omitted, it is `streamable-http`.",),
52
+ ])
53
+
54
+ def apprun(self, logger:logging.Logger, args:argparse.Namespace, tm:float, pf:List[Dict[str, float]]=[]) -> Tuple[int, Dict[str, Any], Any]:
55
+ """
56
+ この機能の実行を行います
57
+
58
+ Args:
59
+ logger (logging.Logger): ロガー
60
+ args (argparse.Namespace): 引数
61
+ tm (float): 実行開始時間
62
+ pf (List[Dict[str, float]]): 呼出元のパフォーマンス情報
63
+
64
+ Returns:
65
+ Tuple[int, Dict[str, Any], Any]: 終了コード, 結果, オブジェクト
66
+ """
67
+ if not hasattr(args, 'mcpserver_name'):
68
+ args.mcpserver_name = 'mcpserver'
69
+ if not hasattr(args, 'mcpserver_url'):
70
+ args.mcpserver_url = 'http://localhost:8081/mcpsv/mcp'
71
+ if not hasattr(args, 'mcpserver_transport'):
72
+ args.mcpserver_transport = 'streamable-http'
73
+ if not hasattr(args, 'mcpserver_apikey'):
74
+ args.mcpserver_apikey = None
75
+
76
+ from fastmcp import FastMCP
77
+ config = dict(
78
+ mcpServers=dict(
79
+ default=dict(
80
+ url=args.mcpserver_url,
81
+ transport=args.mcpserver_transport,
82
+ auth=args.mcpserver_apikey
83
+ )
84
+ )
85
+ )
86
+ try:
87
+ common.reset_logger('FastMCP.fastmcp.server.server')
88
+ proxy = FastMCP.as_proxy(config, name="Config-Based Proxy")
89
+ proxy.run()
90
+ except Exception as e:
91
+ logger.setLevel(logging.ERROR)
92
+ for h in logger.handlers:
93
+ h.setLevel(logging.ERROR)
94
+ logger.error(f"Failed to start MCP proxy: {e}", exc_info=True)
95
+ return self.RESP_ERROR, dict(warn=f"Failed to start MCP proxy: {e}"), None
96
+ return self.RESP_SUCCESS, dict(info="MCP proxy successfully."), None
@@ -99,7 +99,7 @@ class CmdList(feature.OneshotResultEdgeFeature):
99
99
  cmd_list = [dict(title=r.get('title',''), mode=r['mode'], cmd=r['cmd'],
100
100
  description=r.get('description','') + options.get_cmd_attr(r['mode'], r['cmd'], 'description_ja' if is_japan else 'description_en'),
101
101
  tag=r.get('tag','')) for r in cmd_list \
102
- if signin.Signin._check_cmd(self.signin_file_data, args.groups, r['mode'], r['cmd'], logger)]
102
+ if signin.Signin._check_cmd(self.signin_file_data, args.groups, r['mode'], r['cmd'], logger)]
103
103
  ret = dict(success=cmd_list)
104
104
 
105
105
  common.print_format(ret, args.format, tm, args.output_json, args.output_json_append, pf=pf)
@@ -33,7 +33,7 @@ class ServerList(feature.OneshotResultEdgeFeature):
33
33
  Dict[str, Any]: オプション
34
34
  """
35
35
  return dict(
36
- use_redis=self.USE_REDIS_TRUE, nouse_webmode=False,
36
+ use_redis=self.USE_REDIS_TRUE, nouse_webmode=False, use_agent=True,
37
37
  description_ja="起動中のサーバーの一覧を表示します。クライアント環境からの利用も可能です。",
38
38
  description_en="Displays a list of running inference servers. Can also be used from the client environment.",
39
39
  choice=[
@@ -6,7 +6,7 @@ import argparse
6
6
  import logging
7
7
 
8
8
 
9
- class ServerStart(feature.OneshotNotifyEdgeFeature):
9
+ class ServerStart(feature.UnsupportEdgeFeature):
10
10
  def get_mode(self) -> Union[str, List[str]]:
11
11
  """
12
12
  この機能のモードを返します
@@ -33,7 +33,7 @@ class ServerStart(feature.OneshotNotifyEdgeFeature):
33
33
  Dict[str, Any]: オプション
34
34
  """
35
35
  return dict(
36
- use_redis=self.USE_REDIS_TRUE, nouse_webmode=True,
36
+ use_redis=self.USE_REDIS_TRUE, nouse_webmode=True, use_agent=False,
37
37
  description_ja="サーバーを起動します。installモードで `cmdbox -m install -c server` を実行している場合は、 `docker-compose up -d` を使用してください。",
38
38
  description_en="Start the inference server. If you are running `cmdbox -m install -c server` in install mode, use `docker-compose up -d`.",
39
39
  choice=[
@@ -7,7 +7,7 @@ import argparse
7
7
  import logging
8
8
 
9
9
 
10
- class ServerStop(feature.OneshotNotifyEdgeFeature):
10
+ class ServerStop(feature.UnsupportEdgeFeature):
11
11
  def get_mode(self) -> Union[str, List[str]]:
12
12
  """
13
13
  この機能のモードを返します
@@ -34,7 +34,7 @@ class ServerStop(feature.OneshotNotifyEdgeFeature):
34
34
  Dict[str, Any]: オプション
35
35
  """
36
36
  return dict(
37
- use_redis=self.USE_REDIS_TRUE, nouse_webmode=True,
37
+ use_redis=self.USE_REDIS_TRUE, nouse_webmode=True, use_agent=False,
38
38
  description_ja="サーバーを停止します。installモードで `cmdbox -m install -c server` を実行している場合は、 `docker-compose down` を使用してください。",
39
39
  description_en="Stop the inference server. If you are running `cmdbox -m install -c server` in install mode, use `docker-compose down`.",
40
40
  choice=[
@@ -41,7 +41,7 @@ class TtsInstall(feature.UnsupportEdgeFeature):
41
41
  Dict[str, Any]: オプション
42
42
  """
43
43
  return dict(
44
- use_redis=self.USE_REDIS_MEIGHT, nouse_webmode=False, use_agent=False,
44
+ use_redis=self.USE_REDIS_MEIGHT, nouse_webmode=True, use_agent=False,
45
45
  description_ja="Text-to-Speech(TTS)エンジンをインストールします。",
46
46
  description_en="Installs the Text-to-Speech (TTS) engine.",
47
47
  choice=[
@@ -247,6 +247,8 @@ class TtsInstall(feature.UnsupportEdgeFeature):
247
247
  voicevox_dir.mkdir(parents=True, exist_ok=True)
248
248
  dlfile = voicevox_dir / dlfile
249
249
  # ダウンローダーを保存
250
+ if logger.level == logging.DEBUG:
251
+ logger.debug(f"Downloading.. : {downloader_url}")
250
252
  responce = requests.get(downloader_url, allow_redirects=True)
251
253
  if responce.status_code != 200:
252
254
  _msg = f"Failed to download VoiceVox core: {responce.status_code} {responce.reason}. {downloader_url}"
@@ -264,6 +266,8 @@ class TtsInstall(feature.UnsupportEdgeFeature):
264
266
  cmd_line.extend(['--devices', 'directml'])
265
267
  elif voicevox_device == 'cuda':
266
268
  cmd_line.extend(['--devices', 'cuda'])
269
+ if logger.level == logging.DEBUG:
270
+ logger.debug(f"EXEC - {cmd_line}")
267
271
  proc = subprocess.Popen(cmd_line, cwd=str(voicevox_dir), stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
268
272
  outs, errs = proc.communicate(input=b'y\ny\n') # 'y' to confirm installation
269
273
  if proc.returncode != 0:
@@ -272,6 +276,8 @@ class TtsInstall(feature.UnsupportEdgeFeature):
272
276
  _msg += f"Failed to install VoiceVox core: {_msg}"
273
277
  logger.error(_msg, exc_info=True)
274
278
  return dict(warn=_msg)
279
+ if logger.level == logging.DEBUG:
280
+ logger.debug(f"Completed - {cmd_line}")
275
281
  if (voicevox_dir / 'voicevox_core').exists():
276
282
  for file in glob.glob(str(voicevox_dir / 'voicevox_core' / '*')):
277
283
  shutil.move(file, voicevox_dir)
@@ -283,6 +289,8 @@ class TtsInstall(feature.UnsupportEdgeFeature):
283
289
  whl_url = f'https://github.com/VOICEVOX/voicevox_core/releases/download/{voicevox_ver}/{voicevox_whl}'
284
290
  voicevox_whl = voicevox_dir / voicevox_whl
285
291
  # whlファイルをダウンロード
292
+ if logger.level == logging.DEBUG:
293
+ logger.debug(f"Downloading.. : {whl_url}")
286
294
  responce = requests.get(whl_url, allow_redirects=True)
287
295
  if responce.status_code != 200:
288
296
  _msg = f"Failed to download VoiceVox whl: {responce.status_code} {responce.reason}. {whl_url}"
@@ -291,6 +299,8 @@ class TtsInstall(feature.UnsupportEdgeFeature):
291
299
  with open(voicevox_whl, mode='wb') as f:
292
300
  f.write(responce.content)
293
301
  # whlファイルをpipでインストール
302
+ if logger.level == logging.DEBUG:
303
+ logger.debug(f"pip install {voicevox_whl}")
294
304
  rescode = pip.main(['install', str(voicevox_whl)]) # pipのインストール
295
305
  logger.info(f"Install wheel: {voicevox_whl}")
296
306
  if rescode != 0:
@@ -8,7 +8,7 @@ import argparse
8
8
  import logging
9
9
 
10
10
 
11
- class TtsStart(feature.OneshotNotifyEdgeFeature):
11
+ class TtsStart(feature.UnsupportEdgeFeature):
12
12
  VOICEVOX_STYLE = dict()
13
13
  VOICEVOX_STYLE['0.vvm_2'] = dict(fn='0.vvm',ch='四国めたん',md='ノーマル',st=2)
14
14
  VOICEVOX_STYLE['0.vvm_0'] = dict(fn='0.vvm',ch='四国めたん',md='あまあま',st=0)
@@ -33,7 +33,7 @@ class WebApikeyAdd(feature.UnsupportEdgeFeature):
33
33
  Dict[str, Any]: オプション
34
34
  """
35
35
  return dict(
36
- use_redis=self.USE_REDIS_MEIGHT, nouse_webmode=False,
36
+ use_redis=self.USE_REDIS_MEIGHT, nouse_webmode=False, use_agent=False,
37
37
  description_ja="WebモードのユーザーのApiKeyを追加します。",
38
38
  description_en="Add an ApiKey for a user in Web mode.",
39
39
  choice=[
@@ -33,7 +33,7 @@ class WebApikeyDel(feature.UnsupportEdgeFeature):
33
33
  Dict[str, Any]: オプション
34
34
  """
35
35
  return dict(
36
- use_redis=self.USE_REDIS_MEIGHT, nouse_webmode=False,
36
+ use_redis=self.USE_REDIS_MEIGHT, nouse_webmode=False, use_agent=False,
37
37
  description_ja="WebモードのユーザーのApiKeyを削除します。",
38
38
  description_en="Del an ApiKey for a user in Web mode.",
39
39
  choice=[
@@ -38,7 +38,7 @@ class WebGencert(feature.UnsupportEdgeFeature):
38
38
  Dict[str, Any]: オプション
39
39
  """
40
40
  return dict(
41
- use_redis=self.USE_REDIS_FALSE, nouse_webmode=False,
41
+ use_redis=self.USE_REDIS_FALSE, nouse_webmode=False, use_agent=False,
42
42
  description_ja="webモードでSSLを簡易的に実装するために自己署名証明書を生成します。",
43
43
  description_en="Generate a self-signed certificate for simple implementation of SSL in web mode.",
44
44
  choice=[