Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a014965
Rust plugins
crivetimihai Oct 14, 2025
637d0c1
Rust plugins
crivetimihai Oct 14, 2025
30c5ace
Rust plugins
crivetimihai Oct 14, 2025
b290020
Rust plugins
crivetimihai Oct 14, 2025
2608e30
Rust plugins
crivetimihai Oct 14, 2025
f70c8ea
Rust plugins docker build
crivetimihai Oct 15, 2025
956e1e4
Rust plugins docker build
crivetimihai Oct 15, 2025
f62ada3
Rust plugins docker build
crivetimihai Oct 15, 2025
f7ce8ff
Rust plugins docker build
crivetimihai Oct 15, 2025
fb9174b
Rust plugins docker build
crivetimihai Oct 15, 2025
438b294
Rust plugins docker build
crivetimihai Oct 16, 2025
0274b62
Toggle permissions fix (#1250)
kevalmahajan Oct 14, 2025
902243b
feat: Tool output schema support in db and service layer (#1263)
mattsanchez Oct 16, 2025
82bd25e
UI updates for 1258 (#1269)
crivetimihai Oct 16, 2025
a20be5c
Permissions
crivetimihai Oct 17, 2025
1149071
feat: plugin client server mtls support (#1196)
terylt Oct 17, 2025
6490f37
Fix catalog not showing (#1275)
crivetimihai Oct 17, 2025
54dcb34
Fix catalog not showing (#1276)
crivetimihai Oct 17, 2025
b72c8a0
Add support to build MCP Gateway for s390x platform (#1206)
p4yl04d3r Oct 17, 2025
7a7301a
Fix jti consistency for token creation (#1255)
kevalmahajan Oct 17, 2025
fc43dde
Fix test warnings and mypy type issues (#1268)
jonpspri Oct 17, 2025
e4a3123
chore: add linters for plugins and fix linting issues (#1240)
araujof Oct 17, 2025
38299a2
Fix JWT token follows default variable payload expiry instead of UI (…
kevalmahajan Oct 17, 2025
3a7d985
fix: align cookie scope and RBAC redirects with app root path (#1252)
ESnark Oct 17, 2025
a1267f1
Fix UI margins (#1272)
rh-rahulshetty Oct 17, 2025
b6f34f9
Add CI/CD verification for complete build pipeline (#1257)
jonpspri Oct 17, 2025
81effbe
Rest pass api 746 (#1273)
rakdutta Oct 18, 2025
0311b66
Update CHANGELOG
crivetimihai Oct 18, 2025
acc6247
Consolidate and enhance linting tool configuration (#1271)
jonpspri Oct 18, 2025
a5487d5
Run linters
crivetimihai Oct 18, 2025
3515105
Run linters
crivetimihai Oct 18, 2025
97885e2
fix: gateway test failures and warnings (#1281)
jonpspri Oct 18, 2025
94cc3bc
fix: Testing logger intermittent failures and type-checking in auth (…
jonpspri Oct 18, 2025
7a301a5
Enforce Scoped Uniqueness for Prompts, Resources, and A2A Agents; Upd…
rakdutta Oct 19, 2025
b694ca2
REST API and UI pagination (#1277)
madhav165 Oct 19, 2025
0a94110
fix: Intermittent logger failure in test_passthrough_headers (#1284)
jonpspri Oct 19, 2025
5999d1c
Update CHANGELOG and README (#1288)
crivetimihai Oct 19, 2025
ba89c3a
Feat: gRPC-to-MCP Protocol Translation closes #1171 (#1172)
crivetimihai Oct 19, 2025
c255015
feat: Make Rust plugin builds optional with ENABLE_RUST_BUILD flag
crivetimihai Oct 19, 2025
7ef8052
docs: Update documentation for optional Rust plugin builds
crivetimihai Oct 19, 2025
66657eb
feat: Make Rust plugin builds optional in containers
crivetimihai Oct 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
81 changes: 80 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,15 @@ PLUGINS_ENABLED=true
# Default: plugins/config.yaml
PLUGIN_CONFIG_FILE=plugins/config.yaml

# Optional defaults for mTLS when connecting to external MCP plugins (STREAMABLEHTTP transport)
# Provide file paths inside the container. Plugin-specific TLS blocks override these defaults.
# PLUGINS_MTLS_CA_BUNDLE=/app/certs/plugins/ca.crt
# PLUGINS_MTLS_CLIENT_CERT=/app/certs/plugins/gateway-client.pem
# PLUGINS_MTLS_CLIENT_KEY=/app/certs/plugins/gateway-client.key
# PLUGINS_MTLS_CLIENT_KEY_PASSWORD=
# PLUGINS_MTLS_VERIFY=true
# PLUGINS_MTLS_CHECK_HOSTNAME=true

#####################################
# Well-Known URI Configuration
#####################################
Expand Down Expand Up @@ -883,7 +892,6 @@ REQUIRE_STRONG_SECRETS=false
# NOT RECOMMENDED for production!
# REQUIRE_STRONG_SECRETS=false


#####################################
# LLM Chat MCP Client Configuration
#####################################
Expand Down Expand Up @@ -952,3 +960,74 @@ LLMCHAT_ENABLED=false
# OLLAMA_MODEL=llama3
# OLLAMA_BASE_URL=http://localhost:11434
# OLLAMA_TEMPERATURE=0.7

#####################################
# Pagination Configuration
#####################################

# Default number of items per page for paginated endpoints
# Applies to: tools, resources, prompts, servers, gateways, users, teams, tokens, etc.
# Default: 50, Min: 1, Max: 1000
PAGINATION_DEFAULT_PAGE_SIZE=50

# Maximum allowed items per page (prevents abuse)
# Default: 500, Min: 1, Max: 10000
PAGINATION_MAX_PAGE_SIZE=500

# Minimum items per page
# Default: 1
PAGINATION_MIN_PAGE_SIZE=1

# Threshold for switching from offset to cursor-based pagination
# When result set exceeds this count, use cursor-based pagination for performance
# Default: 10000
PAGINATION_CURSOR_THRESHOLD=10000

# Enable cursor-based pagination globally
# Options: true (default), false
# When false, only offset-based pagination is used
PAGINATION_CURSOR_ENABLED=true

# Default sort field for paginated queries
# Default: created_at
PAGINATION_DEFAULT_SORT_FIELD=created_at

# Default sort order for paginated queries
# Options: asc, desc (default)
PAGINATION_DEFAULT_SORT_ORDER=desc

# Maximum offset allowed for offset-based pagination (prevents abuse)
# Default: 100000 (100K records)
PAGINATION_MAX_OFFSET=100000

# Cache pagination counts for performance (seconds)
# Set to 0 to disable caching
# Default: 300 (5 minutes)
PAGINATION_COUNT_CACHE_TTL=300

# Enable pagination links in API responses
# Options: true (default), false
PAGINATION_INCLUDE_LINKS=true

# Base URL for pagination links (defaults to request URL)
# PAGINATION_BASE_URL=https://api.example.com

#####################################
# gRPC Support Settings (EXPERIMENTAL)
#####################################

# Enable gRPC to MCP translation support (disabled by default)
# Requires: pip install mcp-contextforge-gateway[grpc]
# MCPGATEWAY_GRPC_ENABLED=false

# Enable gRPC server reflection by default for service discovery
# MCPGATEWAY_GRPC_REFLECTION_ENABLED=true

# Maximum gRPC message size in bytes (4MB default)
# MCPGATEWAY_GRPC_MAX_MESSAGE_SIZE=4194304

# Default gRPC call timeout in seconds
# MCPGATEWAY_GRPC_TIMEOUT=30

# Enable TLS for gRPC connections by default
# MCPGATEWAY_GRPC_TLS_ENABLED=false
11 changes: 7 additions & 4 deletions .github/tools/check_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

import sys
import os

# Add the project root to the path (two levels up from .github/tools/)
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, project_root)

from mcpgateway.config import get_settings


def main():
"""Check security configuration and exit with appropriate code."""
try:
Expand All @@ -19,16 +21,16 @@ def main():
print(f"Security Score: {status['security_score']}/100")
print(f"Warnings: {len(status['warnings'])}")

if status['warnings']:
if status["warnings"]:
print("\nSecurity Warnings:")
for warning in status['warnings']:
for warning in status["warnings"]:
print(f" - {warning}")

# Exit with error if score is too low
if status['security_score'] < 60:
if status["security_score"] < 60:
print("\n❌ Security score too low for deployment")
sys.exit(1)
elif status['security_score'] < 80:
elif status["security_score"] < 80:
print("\n⚠️ Security could be improved")
sys.exit(0)
else:
Expand All @@ -39,5 +41,6 @@ def main():
print(f"❌ Security validation failed: {e}")
sys.exit(2)


if __name__ == "__main__":
main()
63 changes: 20 additions & 43 deletions .github/tools/fix_file_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def get_header_template(relative_path: str, authors: str = AUTHORS, include_sheb
Module documentation...
"""''')

return '\n'.join(lines)
return "\n".join(lines)


def _write_file(file_path: Path, content: str) -> None:
Expand Down Expand Up @@ -396,8 +396,9 @@ def show_file_lines(file_path: Path, num_lines: int = 10) -> str:
return f"Error reading file: {e}"


def process_file(file_path: Path, mode: str, authors: str, show_diff: bool = False, debug: bool = False,
require_shebang: Optional[bool] = None, require_encoding: bool = True) -> Optional[Dict[str, Any]]:
def process_file(
file_path: Path, mode: str, authors: str, show_diff: bool = False, debug: bool = False, require_shebang: Optional[bool] = None, require_encoding: bool = True
) -> Optional[Dict[str, Any]]:
"""Check a single file and optionally fix its header.

Args:
Expand Down Expand Up @@ -517,8 +518,7 @@ def process_file(file_path: Path, mode: str, authors: str, show_diff: bool = Fal
line_stripped = line.strip()

# Check if this line is a header field
is_header_field = (any(line_stripped.startswith(field + ":") for field in HEADER_FIELDS) or
line_stripped.startswith("Copyright"))
is_header_field = any(line_stripped.startswith(field + ":") for field in HEADER_FIELDS) or line_stripped.startswith("Copyright")

if is_header_field:
in_header_section = True
Expand All @@ -533,9 +533,7 @@ def process_file(file_path: Path, mode: str, authors: str, show_diff: bool = Fal
# Content before any header section (like module descriptions)
# Look ahead to see if there are headers following
has_headers_following = any(
any(future_line.strip().startswith(field + ":") for field in HEADER_FIELDS) or
future_line.strip().startswith("Copyright")
for future_line in docstring_lines[i+1:]
any(future_line.strip().startswith(field + ":") for field in HEADER_FIELDS) or future_line.strip().startswith("Copyright") for future_line in docstring_lines[i + 1 :]
)
if has_headers_following:
# This is content, headers follow later
Expand All @@ -561,8 +559,8 @@ def process_file(file_path: Path, mode: str, authors: str, show_diff: bool = Fal
new_inner_content += "\n\n" + content_str

# Ensure proper ending with newline before closing quotes
if not new_inner_content.endswith('\n'):
new_inner_content += '\n'
if not new_inner_content.endswith("\n"):
new_inner_content += "\n"

new_docstring = f"{quotes}{new_inner_content}{quotes}"

Expand Down Expand Up @@ -598,12 +596,7 @@ def process_file(file_path: Path, mode: str, authors: str, show_diff: bool = Fal
# Generate new source code for diff preview or actual fixing
if mode in ["fix-all", "fix", "interactive"] or show_diff:
# Create new header
new_header = get_header_template(
relative_path_str,
authors=authors,
include_shebang=shebang_required,
include_encoding=require_encoding
)
new_header = get_header_template(relative_path_str, authors=authors, include_shebang=shebang_required, include_encoding=require_encoding)

# Remove existing shebang/encoding if present
start_line = 0
Expand All @@ -619,12 +612,7 @@ def process_file(file_path: Path, mode: str, authors: str, show_diff: bool = Fal
result: Dict[str, Any] = {"file": relative_path_str, "issues": issues}

if debug:
result["debug"] = {
"executable": file_is_executable,
"has_shebang": has_shebang,
"has_encoding": has_encoding,
"first_lines": show_file_lines(file_path, 5)
}
result["debug"] = {"executable": file_is_executable, "has_shebang": has_shebang, "has_encoding": has_encoding, "first_lines": show_file_lines(file_path, 5)}

if show_diff and new_source_code and new_source_code != source_code:
result["diff"] = generate_diff(source_code, new_source_code, relative_path_str)
Expand Down Expand Up @@ -692,7 +680,7 @@ def parse_arguments(argv: Optional[List[str]] = None) -> argparse.Namespace:
False
"""
parser = argparse.ArgumentParser(
description="Check and fix file headers in Python source files. " "By default, runs in check mode (dry run).",
description="Check and fix file headers in Python source files. By default, runs in check mode (dry run).",
epilog="Examples:\n"
" %(prog)s # Check all files (default)\n"
" %(prog)s --fix-all # Fix all files\n"
Expand All @@ -716,16 +704,13 @@ def parse_arguments(argv: Optional[List[str]] = None) -> argparse.Namespace:

# Header configuration options
header_group = parser.add_argument_group("header configuration")
header_group.add_argument("--require-shebang", choices=["always", "never", "auto"], default="auto",
help="Require shebang line: 'always', 'never', or 'auto' (only for executable files). Default: auto")
header_group.add_argument("--require-encoding", action="store_true", default=True,
help="Require encoding line. Default: True")
header_group.add_argument("--no-encoding", action="store_false", dest="require_encoding",
help="Don't require encoding line.")
header_group.add_argument("--copyright-year", type=int, default=COPYRIGHT_YEAR,
help=f"Copyright year to use. Default: {COPYRIGHT_YEAR}")
header_group.add_argument("--license", type=str, default=LICENSE,
help=f"License identifier to use. Default: {LICENSE}")
header_group.add_argument(
"--require-shebang", choices=["always", "never", "auto"], default="auto", help="Require shebang line: 'always', 'never', or 'auto' (only for executable files). Default: auto"
)
header_group.add_argument("--require-encoding", action="store_true", default=True, help="Require encoding line. Default: True")
header_group.add_argument("--no-encoding", action="store_false", dest="require_encoding", help="Don't require encoding line.")
header_group.add_argument("--copyright-year", type=int, default=COPYRIGHT_YEAR, help=f"Copyright year to use. Default: {COPYRIGHT_YEAR}")
header_group.add_argument("--license", type=str, default=LICENSE, help=f"License identifier to use. Default: {LICENSE}")

return parser.parse_args(argv)

Expand Down Expand Up @@ -882,7 +867,7 @@ def print_results(issues_found: List[Dict[str, Any]], mode: str, modified_count:
print("\nSome files may have been skipped in interactive mode.", file=sys.stderr)
print("To fix all remaining headers, run: make fix-all-headers", file=sys.stderr)
elif modified_count > 0:
print(f"\nSuccessfully fixed {modified_count} file(s). " f"Please re-stage and commit.", file=sys.stderr)
print(f"\nSuccessfully fixed {modified_count} file(s). Please re-stage and commit.", file=sys.stderr)


def main(argv: Optional[List[str]] = None) -> None:
Expand Down Expand Up @@ -972,15 +957,7 @@ def main(argv: Optional[List[str]] = None) -> None:
modified_files_count = 0

for file_path in files_to_process:
result = process_file(
file_path,
mode,
args.authors,
show_diff=args.show_diff,
debug=args.debug,
require_shebang=require_shebang,
require_encoding=args.require_encoding
)
result = process_file(file_path, mode, args.authors, show_diff=args.show_diff, debug=args.debug, require_shebang=require_shebang, require_encoding=args.require_encoding)
if result:
issues_found_in_files.append(result)
if result.get("fixed", False):
Expand Down
27 changes: 6 additions & 21 deletions .github/tools/pin_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"""

# Standard
from pathlib import Path
import re
import sys
import tomllib
Expand Down Expand Up @@ -47,7 +46,7 @@ def pin_requirements(pyproject_path="pyproject.toml", output_path="requirements.
for dep in dependencies:
# Match package name with optional extras and version
# Pattern: package_name[optional_extras]>=version
match = re.match(r'^([a-zA-Z0-9_-]+)(?:\[.*\])?>=(.+)', dep)
match = re.match(r"^([a-zA-Z0-9_-]+)(?:\[.*\])?>=(.+)", dep)

if match:
name, version = match.groups()
Expand Down Expand Up @@ -83,24 +82,10 @@ def main():
# Standard
import argparse

parser = argparse.ArgumentParser(
description="Extract and pin dependencies from pyproject.toml"
)
parser.add_argument(
"-i", "--input",
default="pyproject.toml",
help="Path to pyproject.toml file (default: pyproject.toml)"
)
parser.add_argument(
"-o", "--output",
default="requirements.txt",
help="Path to output requirements file (default: requirements.txt)"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Print dependencies without writing to file"
)
parser = argparse.ArgumentParser(description="Extract and pin dependencies from pyproject.toml")
parser.add_argument("-i", "--input", default="pyproject.toml", help="Path to pyproject.toml file (default: pyproject.toml)")
parser.add_argument("-o", "--output", default="requirements.txt", help="Path to output requirements file (default: requirements.txt)")
parser.add_argument("--dry-run", action="store_true", help="Print dependencies without writing to file")

args = parser.parse_args()

Expand All @@ -117,7 +102,7 @@ def main():
print("Would generate the following pinned dependencies:\n")

for dep in sorted(dependencies, key=lambda x: x.lower()):
match = re.match(r'^([a-zA-Z0-9_-]+)(?:\[.*\])?>=(.+)', dep)
match = re.match(r"^([a-zA-Z0-9_-]+)(?:\[.*\])?>=(.+)", dep)
if match:
name, version = match.groups()
print(f"{name}=={version}")
Expand Down
10 changes: 5 additions & 5 deletions .github/tools/update_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ def update_dependency_array(
elif original == new_dep:
print(f"{COLOR_SUCCESS}Up-to-date: Before: {original} | After: {new_dep}")
else:
print(f"{COLOR_WARNING}Updated: Before: {original} {Style.RESET_ALL}--> " f"{COLOR_SUCCESS}{new_dep}")
print(f"{COLOR_WARNING}Updated: Before: {original} {Style.RESET_ALL}--> {COLOR_SUCCESS}{new_dep}")
if verbose:
logger.info(f"πŸ“ Updated dependency: {original} -> {new_dep}")

Expand Down Expand Up @@ -862,7 +862,7 @@ async def process_requirements(
elif original == updated:
print(f"{COLOR_SUCCESS}Up-to-date: Before: {original} | After: {updated}")
else:
print(f"{COLOR_WARNING}Updated: Before: {original} {Style.RESET_ALL}--> " f"{COLOR_SUCCESS}{updated}")
print(f"{COLOR_WARNING}Updated: Before: {original} {Style.RESET_ALL}--> {COLOR_SUCCESS}{updated}")
if verbose:
logger.info(f"πŸ“ Updated dependency: {original} -> {updated}")

Expand Down Expand Up @@ -1335,7 +1335,7 @@ def main() -> int:
# CLI argument parser
parser = argparse.ArgumentParser(
description=(
"Update dependency version constraints in pyproject.toml to use pinned (==), >=, or <= " "latest versions, preserving comments (unless removed) and optionally sorting dependencies."
"Update dependency version constraints in pyproject.toml to use pinned (==), >=, or <= latest versions, preserving comments (unless removed) and optionally sorting dependencies."
),
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
Expand All @@ -1359,7 +1359,7 @@ def main() -> int:
parser.add_argument(
"--backup",
default=file_config.get("backup", None),
help=("Backup file name. If not specified, a timestamped backup (e.g. .depupdate.1678891234)" " will be created in the current directory."),
help=("Backup file name. If not specified, a timestamped backup (e.g. .depupdate.1678891234) will be created in the current directory."),
)
parser.add_argument(
"--no-backup",
Expand Down Expand Up @@ -1441,7 +1441,7 @@ def main() -> int:
"--version-spec",
choices=["pinned", "gte", "lte"],
default=file_config.get("version_spec", "gte"),
help=("How to update version constraints: 'pinned' uses '==latest', " "'gte' uses '>=latest', 'lte' uses '<=latest'. Default is 'gte'."),
help=("How to update version constraints: 'pinned' uses '==latest', 'gte' uses '>=latest', 'lte' uses '<=latest'. Default is 'gte'."),
)

# Ignore dependencies
Expand Down
Loading