Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ CLAUDE.local.md
.scannerwork
llms-full.txt
aider*
.aider*
todo/
*.sarif
devskim-results.sarif
Expand All @@ -35,7 +34,6 @@ token.txt
mcpgateway.sbom.xml
gateway_service_leader.lock
docs/docs/test/
tmp
*.tgz
*.gz
*.bz
Expand Down Expand Up @@ -74,9 +72,7 @@ dictionary.dic
pdm.lock
.pdm-python
temp/
public/
*history.md
htmlcov
test_commands.md
cover.md
build/
Expand All @@ -99,28 +95,22 @@ scribeflow.log
coverage_re
bin/flagged
flagged/
certs/
# VENV
.python37/
.python39/

# Byte-compiled / optimized / DLL files
__pycache__/
**/__pycache__/
*.py[cod]
*$py.class
mcpgateway-wrapper/src/mcp_gateway_wrapper/__pycache__/

# Bak
*.bak

# C extensions
*.so

# Distribution / packaging
.wily/
.Python
build/
develop-eggs/
dist/
downloads/
Expand Down Expand Up @@ -165,7 +155,6 @@ coverage.xml
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

Expand Down Expand Up @@ -199,8 +188,6 @@ celerybeat-schedule
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
Expand Down Expand Up @@ -231,9 +218,6 @@ dmypy.json

.idea/

# Sonar
.scannerwork

# vim
*.swp
*,cover
Expand All @@ -244,9 +228,6 @@ logging/

.ai*

# downloads
downloads/

# db_path
db_path/

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -530,5 +530,5 @@ repos:
rev: 1.7.0 # or master if you're bold
hooks:
- id: interrogate
args: [--quiet, --fail-under=100]
args: [--quiet, --fail-under=100, --exclude, cforge/_version.py]
files: ^cforge/
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Here are some examples:
cforge tools list [--mcp-server-id ID] [--json]
cforge tools get <tool-id>
cforge tools create [file.json]
cforge tools execute <tool-id> # Interactive schema prompt
cforge tools execute <tool-id> --data args.json # Use JSON args file
cforge tools toggle <tool-id>

# Resources
Expand Down
2 changes: 1 addition & 1 deletion cforge/commands/deploy/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import typer

# First-Party
from cforge.common import get_console
from cforge.common.console import get_console


def deploy() -> None:
Expand Down
8 changes: 3 additions & 5 deletions cforge/commands/metrics/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@
import typer

# First-Party
from cforge.common import (
get_console,
make_authenticated_request,
print_json,
)
from cforge.common.console import get_console
from cforge.common.http import make_authenticated_request
from cforge.common.render import print_json


def metrics_get(
Expand Down
13 changes: 5 additions & 8 deletions cforge/commands/resources/a2a.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@
import typer

# First-Party
from cforge.common import (
get_console,
handle_exception,
make_authenticated_request,
print_json,
print_table,
prompt_for_schema,
)
from cforge.common.console import get_console
from cforge.common.errors import handle_exception
from cforge.common.http import make_authenticated_request
from cforge.common.prompting import prompt_for_schema
from cforge.common.render import print_json, print_table
from mcpgateway.schemas import A2AAgentCreate, A2AAgentUpdate


Expand Down
13 changes: 5 additions & 8 deletions cforge/commands/resources/mcp_servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@
import typer

# First-Party
from cforge.common import (
get_console,
handle_exception,
make_authenticated_request,
print_json,
print_table,
prompt_for_schema,
)
from cforge.common.console import get_console
from cforge.common.errors import handle_exception
from cforge.common.http import make_authenticated_request
from cforge.common.prompting import prompt_for_schema
from cforge.common.render import print_json, print_table
from mcpgateway.schemas import GatewayCreate, GatewayUpdate


Expand Down
77 changes: 34 additions & 43 deletions cforge/commands/resources/plugins.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
"""Location: ./cforge/commands/resources/plugins.py
Copyright 2025
"""
SPDX-License-Identifier: Apache-2.0
Authors: Matthew Grigsby

CLI command group: plugins

Expand All @@ -16,67 +14,59 @@
"""

# Standard
from enum import Enum
from typing import Any, Dict, Optional

# Third-Party
import typer

# First-Party
from cforge.common import (
AuthenticationError,
CLIError,
get_console,
handle_exception,
make_authenticated_request,
print_json,
print_table,
)


class _CaseInsensitiveEnum(str, Enum):
"""Enum that supports case-insensitive parsing for CLI options."""

@classmethod
def _missing_(cls, value: object) -> Optional["_CaseInsensitiveEnum"]:
"""Resolve unknown values by matching enum values case-insensitively.

Typer converts CLI strings into Enum members. Implementing `_missing_`
allows `--mode EnFoRcE` to resolve to `PluginMode.ENFORCE`, while still
rejecting unknown values.
"""
if not isinstance(value, str):
return None
value_folded = value.casefold()
for member in cls:
if member.value.casefold() == value_folded:
return member
return None
from cforge.common.console import get_console
from cforge.common.errors import AuthenticationError, CaseInsensitiveEnum, CLIError, handle_exception
from cforge.common.http import make_authenticated_request
from cforge.common.render import print_json, print_table


class PluginMode(_CaseInsensitiveEnum):
class PluginMode(CaseInsensitiveEnum):
"""Valid plugin mode filters supported by the gateway admin API."""

ENFORCE = "enforce"
PERMISSIVE = "permissive"
DISABLED = "disabled"


def _handle_plugins_exception(exception: Exception) -> None:
def _parse_plugin_mode(mode: Optional[str]) -> Optional[PluginMode]:
"""Parse plugin mode with case-insensitive enum matching."""
if mode is None:
return None
try:
return PluginMode(mode)
except ValueError as exc:
choices = ", ".join(member.value for member in PluginMode)
raise CLIError(f"Invalid value for '--mode': {mode!r}. Must be one of: {choices}.") from exc


def _handle_plugins_exception(exception: Exception, operation: str, plugin_name: Optional[str] = None) -> None:
"""Provide plugin-specific hints and raise a CLI error."""
console = get_console()

if isinstance(exception, AuthenticationError):
console.print("[yellow]Access denied. Requires admin.plugins permission.[/yellow]")
elif isinstance(exception, CLIError) and "(404)" in str(exception):
console.print("[yellow]Admin plugin API unavailable. Ensure MCPGATEWAY_ADMIN_API_ENABLED=true and gateway version supports /admin/plugins.[/yellow]")
elif isinstance(exception, CLIError):
error_str = str(exception)
if "(404)" in error_str:
error_str_folded = error_str.casefold()
if operation == "get" and "plugin" in error_str_folded and "not found" in error_str_folded:
plugin_label = plugin_name or "requested plugin"
console.print(f"[yellow]Plugin not found: {plugin_label}[/yellow]")
else:
console.print("[yellow]Admin plugin API unavailable. Ensure MCPGATEWAY_ADMIN_API_ENABLED=true and gateway version supports /admin/plugins.[/yellow]")

handle_exception(exception)


def plugins_list(
search: Optional[str] = typer.Option(None, "--search", help="Search by plugin name, description, or author"),
mode: Optional[PluginMode] = typer.Option(None, "--mode", help="Filter by mode"),
mode: Optional[str] = typer.Option(None, "--mode", help="Filter by mode"),
hook: Optional[str] = typer.Option(None, "--hook", help="Filter by hook type"),
tag: Optional[str] = typer.Option(None, "--tag", help="Filter by plugin tag"),
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
Expand All @@ -88,8 +78,9 @@ def plugins_list(
params: Dict[str, Any] = {}
if search:
params["search"] = search
if mode:
params["mode"] = mode.value
parsed_mode = _parse_plugin_mode(mode)
if parsed_mode:
params["mode"] = parsed_mode.value
if hook:
params["hook"] = hook
if tag:
Expand All @@ -108,7 +99,7 @@ def plugins_list(
console.print("[yellow]No plugins found[/yellow]")

except Exception as e:
_handle_plugins_exception(e)
_handle_plugins_exception(e, operation="list")


def plugins_get(
Expand All @@ -120,7 +111,7 @@ def plugins_get(
print_json(result, f"Plugin {name}")

except Exception as e:
_handle_plugins_exception(e)
_handle_plugins_exception(e, operation="get", plugin_name=name)


def plugins_stats() -> None:
Expand All @@ -130,4 +121,4 @@ def plugins_stats() -> None:
print_json(result, "Plugin Statistics")

except Exception as e:
_handle_plugins_exception(e)
_handle_plugins_exception(e, operation="stats")
13 changes: 5 additions & 8 deletions cforge/commands/resources/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@
import typer

# First-Party
from cforge.common import (
get_console,
handle_exception,
make_authenticated_request,
print_json,
print_table,
prompt_for_schema,
)
from cforge.common.console import get_console
from cforge.common.errors import handle_exception
from cforge.common.http import make_authenticated_request
from cforge.common.prompting import prompt_for_schema
from cforge.common.render import print_json, print_table
from mcpgateway.schemas import PromptCreate, PromptUpdate


Expand Down
13 changes: 5 additions & 8 deletions cforge/commands/resources/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@
import typer

# First-Party
from cforge.common import (
get_console,
handle_exception,
make_authenticated_request,
print_json,
print_table,
prompt_for_schema,
)
from cforge.common.console import get_console
from cforge.common.errors import handle_exception
from cforge.common.http import make_authenticated_request
from cforge.common.prompting import prompt_for_schema
from cforge.common.render import print_json, print_table
from mcpgateway.schemas import ResourceCreate, ResourceUpdate


Expand Down
Loading