Skip to content

Commit 33b6e50

Browse files
authored
Merge pull request #22 from MatthewGrigsby/feat/tools-execute-cli
feat: add CLI tool execution with dynamic schema prompting
2 parents 1dd3e12 + bb13b05 commit 33b6e50

46 files changed

Lines changed: 4690 additions & 1938 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ CLAUDE.local.md
1818
.scannerwork
1919
llms-full.txt
2020
aider*
21-
.aider*
2221
todo/
2322
*.sarif
2423
devskim-results.sarif
@@ -35,7 +34,6 @@ token.txt
3534
mcpgateway.sbom.xml
3635
gateway_service_leader.lock
3736
docs/docs/test/
38-
tmp
3937
*.tgz
4038
*.gz
4139
*.bz
@@ -74,9 +72,7 @@ dictionary.dic
7472
pdm.lock
7573
.pdm-python
7674
temp/
77-
public/
7875
*history.md
79-
htmlcov
8076
test_commands.md
8177
cover.md
8278
build/
@@ -99,28 +95,22 @@ scribeflow.log
9995
coverage_re
10096
bin/flagged
10197
flagged/
102-
certs/
10398
# VENV
10499
.python37/
105100
.python39/
106101

107102
# Byte-compiled / optimized / DLL files
108103
__pycache__/
109-
**/__pycache__/
110104
*.py[cod]
111105
*$py.class
112106
mcpgateway-wrapper/src/mcp_gateway_wrapper/__pycache__/
113107

114-
# Bak
115-
*.bak
116-
117108
# C extensions
118109
*.so
119110

120111
# Distribution / packaging
121112
.wily/
122113
.Python
123-
build/
124114
develop-eggs/
125115
dist/
126116
downloads/
@@ -165,7 +155,6 @@ coverage.xml
165155
*.pot
166156

167157
# Django stuff:
168-
*.log
169158
local_settings.py
170159
db.sqlite3
171160

@@ -199,8 +188,6 @@ celerybeat-schedule
199188
*.sage.py
200189

201190
# Environments
202-
.env
203-
.venv
204191
env/
205192
venv/
206193
ENV/
@@ -231,9 +218,6 @@ dmypy.json
231218

232219
.idea/
233220

234-
# Sonar
235-
.scannerwork
236-
237221
# vim
238222
*.swp
239223
*,cover
@@ -244,9 +228,6 @@ logging/
244228

245229
.ai*
246230

247-
# downloads
248-
downloads/
249-
250231
# db_path
251232
db_path/
252233

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,5 +530,5 @@ repos:
530530
rev: 1.7.0 # or master if you're bold
531531
hooks:
532532
- id: interrogate
533-
args: [--quiet, --fail-under=100]
533+
args: [--quiet, --fail-under=100, --exclude, cforge/_version.py]
534534
files: ^cforge/

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ Here are some examples:
8383
cforge tools list [--mcp-server-id ID] [--json]
8484
cforge tools get <tool-id>
8585
cforge tools create [file.json]
86+
cforge tools execute <tool-id> # Interactive schema prompt
87+
cforge tools execute <tool-id> --data args.json # Use JSON args file
8688
cforge tools toggle <tool-id>
8789

8890
# Resources

cforge/commands/deploy/deploy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import typer
1212

1313
# First-Party
14-
from cforge.common import get_console
14+
from cforge.common.console import get_console
1515

1616

1717
def deploy() -> None:

cforge/commands/metrics/metrics.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@
1111
import typer
1212

1313
# First-Party
14-
from cforge.common import (
15-
get_console,
16-
make_authenticated_request,
17-
print_json,
18-
)
14+
from cforge.common.console import get_console
15+
from cforge.common.http import make_authenticated_request
16+
from cforge.common.render import print_json
1917

2018

2119
def metrics_get(

cforge/commands/resources/a2a.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
import typer
1717

1818
# First-Party
19-
from cforge.common import (
20-
get_console,
21-
handle_exception,
22-
make_authenticated_request,
23-
print_json,
24-
print_table,
25-
prompt_for_schema,
26-
)
19+
from cforge.common.console import get_console
20+
from cforge.common.errors import handle_exception
21+
from cforge.common.http import make_authenticated_request
22+
from cforge.common.prompting import prompt_for_schema
23+
from cforge.common.render import print_json, print_table
2724
from mcpgateway.schemas import A2AAgentCreate, A2AAgentUpdate
2825

2926

cforge/commands/resources/mcp_servers.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
import typer
1717

1818
# First-Party
19-
from cforge.common import (
20-
get_console,
21-
handle_exception,
22-
make_authenticated_request,
23-
print_json,
24-
print_table,
25-
prompt_for_schema,
26-
)
19+
from cforge.common.console import get_console
20+
from cforge.common.errors import handle_exception
21+
from cforge.common.http import make_authenticated_request
22+
from cforge.common.prompting import prompt_for_schema
23+
from cforge.common.render import print_json, print_table
2724
from mcpgateway.schemas import GatewayCreate, GatewayUpdate
2825

2926

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# -*- coding: utf-8 -*-
2-
"""Location: ./cforge/commands/resources/plugins.py
3-
Copyright 2025
2+
"""
43
SPDX-License-Identifier: Apache-2.0
5-
Authors: Matthew Grigsby
64
75
CLI command group: plugins
86
@@ -16,67 +14,59 @@
1614
"""
1715

1816
# Standard
19-
from enum import Enum
2017
from typing import Any, Dict, Optional
2118

2219
# Third-Party
2320
import typer
2421

2522
# First-Party
26-
from cforge.common import (
27-
AuthenticationError,
28-
CLIError,
29-
get_console,
30-
handle_exception,
31-
make_authenticated_request,
32-
print_json,
33-
print_table,
34-
)
35-
36-
37-
class _CaseInsensitiveEnum(str, Enum):
38-
"""Enum that supports case-insensitive parsing for CLI options."""
39-
40-
@classmethod
41-
def _missing_(cls, value: object) -> Optional["_CaseInsensitiveEnum"]:
42-
"""Resolve unknown values by matching enum values case-insensitively.
43-
44-
Typer converts CLI strings into Enum members. Implementing `_missing_`
45-
allows `--mode EnFoRcE` to resolve to `PluginMode.ENFORCE`, while still
46-
rejecting unknown values.
47-
"""
48-
if not isinstance(value, str):
49-
return None
50-
value_folded = value.casefold()
51-
for member in cls:
52-
if member.value.casefold() == value_folded:
53-
return member
54-
return None
23+
from cforge.common.console import get_console
24+
from cforge.common.errors import AuthenticationError, CaseInsensitiveEnum, CLIError, handle_exception
25+
from cforge.common.http import make_authenticated_request
26+
from cforge.common.render import print_json, print_table
5527

5628

57-
class PluginMode(_CaseInsensitiveEnum):
29+
class PluginMode(CaseInsensitiveEnum):
5830
"""Valid plugin mode filters supported by the gateway admin API."""
5931

6032
ENFORCE = "enforce"
6133
PERMISSIVE = "permissive"
6234
DISABLED = "disabled"
6335

6436

65-
def _handle_plugins_exception(exception: Exception) -> None:
37+
def _parse_plugin_mode(mode: Optional[str]) -> Optional[PluginMode]:
38+
"""Parse plugin mode with case-insensitive enum matching."""
39+
if mode is None:
40+
return None
41+
try:
42+
return PluginMode(mode)
43+
except ValueError as exc:
44+
choices = ", ".join(member.value for member in PluginMode)
45+
raise CLIError(f"Invalid value for '--mode': {mode!r}. Must be one of: {choices}.") from exc
46+
47+
48+
def _handle_plugins_exception(exception: Exception, operation: str, plugin_name: Optional[str] = None) -> None:
6649
"""Provide plugin-specific hints and raise a CLI error."""
6750
console = get_console()
6851

6952
if isinstance(exception, AuthenticationError):
7053
console.print("[yellow]Access denied. Requires admin.plugins permission.[/yellow]")
71-
elif isinstance(exception, CLIError) and "(404)" in str(exception):
72-
console.print("[yellow]Admin plugin API unavailable. Ensure MCPGATEWAY_ADMIN_API_ENABLED=true and gateway version supports /admin/plugins.[/yellow]")
54+
elif isinstance(exception, CLIError):
55+
error_str = str(exception)
56+
if "(404)" in error_str:
57+
error_str_folded = error_str.casefold()
58+
if operation == "get" and "plugin" in error_str_folded and "not found" in error_str_folded:
59+
plugin_label = plugin_name or "requested plugin"
60+
console.print(f"[yellow]Plugin not found: {plugin_label}[/yellow]")
61+
else:
62+
console.print("[yellow]Admin plugin API unavailable. Ensure MCPGATEWAY_ADMIN_API_ENABLED=true and gateway version supports /admin/plugins.[/yellow]")
7363

7464
handle_exception(exception)
7565

7666

7767
def plugins_list(
7868
search: Optional[str] = typer.Option(None, "--search", help="Search by plugin name, description, or author"),
79-
mode: Optional[PluginMode] = typer.Option(None, "--mode", help="Filter by mode"),
69+
mode: Optional[str] = typer.Option(None, "--mode", help="Filter by mode"),
8070
hook: Optional[str] = typer.Option(None, "--hook", help="Filter by hook type"),
8171
tag: Optional[str] = typer.Option(None, "--tag", help="Filter by plugin tag"),
8272
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
@@ -88,8 +78,9 @@ def plugins_list(
8878
params: Dict[str, Any] = {}
8979
if search:
9080
params["search"] = search
91-
if mode:
92-
params["mode"] = mode.value
81+
parsed_mode = _parse_plugin_mode(mode)
82+
if parsed_mode:
83+
params["mode"] = parsed_mode.value
9384
if hook:
9485
params["hook"] = hook
9586
if tag:
@@ -108,7 +99,7 @@ def plugins_list(
10899
console.print("[yellow]No plugins found[/yellow]")
109100

110101
except Exception as e:
111-
_handle_plugins_exception(e)
102+
_handle_plugins_exception(e, operation="list")
112103

113104

114105
def plugins_get(
@@ -120,7 +111,7 @@ def plugins_get(
120111
print_json(result, f"Plugin {name}")
121112

122113
except Exception as e:
123-
_handle_plugins_exception(e)
114+
_handle_plugins_exception(e, operation="get", plugin_name=name)
124115

125116

126117
def plugins_stats() -> None:
@@ -130,4 +121,4 @@ def plugins_stats() -> None:
130121
print_json(result, "Plugin Statistics")
131122

132123
except Exception as e:
133-
_handle_plugins_exception(e)
124+
_handle_plugins_exception(e, operation="stats")

cforge/commands/resources/prompts.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
import typer
1717

1818
# First-Party
19-
from cforge.common import (
20-
get_console,
21-
handle_exception,
22-
make_authenticated_request,
23-
print_json,
24-
print_table,
25-
prompt_for_schema,
26-
)
19+
from cforge.common.console import get_console
20+
from cforge.common.errors import handle_exception
21+
from cforge.common.http import make_authenticated_request
22+
from cforge.common.prompting import prompt_for_schema
23+
from cforge.common.render import print_json, print_table
2724
from mcpgateway.schemas import PromptCreate, PromptUpdate
2825

2926

cforge/commands/resources/resources.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
import typer
1717

1818
# First-Party
19-
from cforge.common import (
20-
get_console,
21-
handle_exception,
22-
make_authenticated_request,
23-
print_json,
24-
print_table,
25-
prompt_for_schema,
26-
)
19+
from cforge.common.console import get_console
20+
from cforge.common.errors import handle_exception
21+
from cforge.common.http import make_authenticated_request
22+
from cforge.common.prompting import prompt_for_schema
23+
from cforge.common.render import print_json, print_table
2724
from mcpgateway.schemas import ResourceCreate, ResourceUpdate
2825

2926

0 commit comments

Comments
 (0)