@raghulm/aegis-mcp 1.0.5 → 1.0.9

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.
package/server/config.py CHANGED
@@ -1,82 +1,82 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import os
5
- from dataclasses import dataclass
6
- from pathlib import Path
7
- from typing import Any
8
-
9
-
10
- @dataclass(frozen=True)
11
- class Settings:
12
- """Runtime configuration for the MCP service."""
13
-
14
- service_name: str = "aegis"
15
- environment: str = "dev"
16
- policy_roles_path: Path = Path("policies/roles.yaml")
17
- policy_scopes_path: Path = Path("policies/scope_rules.yaml")
18
- oidc_issuer: str | None = None
19
- oidc_audience: str | None = None
20
-
21
-
22
-
23
- def _parse_simple_yaml(raw: str) -> dict[str, Any]:
24
- """Very small YAML subset parser for key/list policy files."""
25
- root: dict[str, Any] = {}
26
- current_section: dict[str, list[str]] | None = None
27
- current_key: str | None = None
28
- for line in raw.splitlines():
29
- stripped = line.rstrip()
30
- if not stripped or stripped.lstrip().startswith("#"):
31
- continue
32
- if not line.startswith(" ") and stripped.endswith(":"):
33
- section = stripped[:-1]
34
- root[section] = {}
35
- current_section = root[section]
36
- current_key = None
37
- elif current_section is not None and line.startswith(" ") and stripped.endswith(":"):
38
- current_key = stripped[:-1]
39
- current_section[current_key] = []
40
- elif current_section is not None and current_key and line.strip().startswith("-"):
41
- value = line.split("-", maxsplit=1)[1].strip().strip('"')
42
- current_section[current_key].append(value)
43
- return root
44
-
45
-
46
-
47
- def _load_yaml(path: Path) -> dict[str, Any]:
48
- if not path.exists():
49
- return {}
50
- content = path.read_text(encoding="utf-8")
51
- try:
52
- import yaml # type: ignore
53
-
54
- return yaml.safe_load(content) or {}
55
- except Exception:
56
- return _parse_simple_yaml(content)
57
-
58
-
59
-
60
- def load_settings() -> Settings:
61
- return Settings(
62
- service_name=os.getenv("MCP_SERVICE_NAME", "aegis"),
63
- environment=os.getenv("MCP_ENV", "dev"),
64
- policy_roles_path=Path(os.getenv("MCP_ROLES_FILE", "policies/roles.yaml")),
65
- policy_scopes_path=Path(os.getenv("MCP_SCOPES_FILE", "policies/scope_rules.yaml")),
66
- oidc_issuer=os.getenv("OIDC_ISSUER"),
67
- oidc_audience=os.getenv("OIDC_AUDIENCE"),
68
- )
69
-
70
-
71
-
72
- def load_role_policies(settings: Settings) -> dict[str, list[str]]:
73
- raw = _load_yaml(settings.policy_roles_path)
74
- roles = raw.get("roles", {})
75
- return {str(k): [str(v) for v in values] for k, values in roles.items()}
76
-
77
-
78
-
79
- def load_scope_policies(settings: Settings) -> dict[str, list[str]]:
80
- raw = _load_yaml(settings.policy_scopes_path)
81
- scopes = raw.get("scopes", {})
82
- return {str(k): [str(v) for v in values] for k, values in scopes.items()}
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class Settings:
12
+ """Runtime configuration for the MCP service."""
13
+
14
+ service_name: str = "aegis"
15
+ environment: str = "dev"
16
+ policy_roles_path: Path = Path("policies/roles.yaml")
17
+ policy_scopes_path: Path = Path("policies/scope_rules.yaml")
18
+ oidc_issuer: str | None = None
19
+ oidc_audience: str | None = None
20
+
21
+
22
+
23
+ def _parse_simple_yaml(raw: str) -> dict[str, Any]:
24
+ """Very small YAML subset parser for key/list policy files."""
25
+ root: dict[str, Any] = {}
26
+ current_section: dict[str, list[str]] | None = None
27
+ current_key: str | None = None
28
+ for line in raw.splitlines():
29
+ stripped = line.rstrip()
30
+ if not stripped or stripped.lstrip().startswith("#"):
31
+ continue
32
+ if not line.startswith(" ") and stripped.endswith(":"):
33
+ section = stripped[:-1]
34
+ root[section] = {}
35
+ current_section = root[section]
36
+ current_key = None
37
+ elif current_section is not None and line.startswith(" ") and stripped.endswith(":"):
38
+ current_key = stripped[:-1]
39
+ current_section[current_key] = []
40
+ elif current_section is not None and current_key and line.strip().startswith("-"):
41
+ value = line.split("-", maxsplit=1)[1].strip().strip('"')
42
+ current_section[current_key].append(value)
43
+ return root
44
+
45
+
46
+
47
+ def _load_yaml(path: Path) -> dict[str, Any]:
48
+ if not path.exists():
49
+ return {}
50
+ content = path.read_text(encoding="utf-8")
51
+ try:
52
+ import yaml # type: ignore
53
+
54
+ return yaml.safe_load(content) or {}
55
+ except Exception:
56
+ return _parse_simple_yaml(content)
57
+
58
+
59
+
60
+ def load_settings() -> Settings:
61
+ return Settings(
62
+ service_name=os.getenv("MCP_SERVICE_NAME", "aegis"),
63
+ environment=os.getenv("MCP_ENV", "dev"),
64
+ policy_roles_path=Path(os.getenv("MCP_ROLES_FILE", "policies/roles.yaml")),
65
+ policy_scopes_path=Path(os.getenv("MCP_SCOPES_FILE", "policies/scope_rules.yaml")),
66
+ oidc_issuer=os.getenv("OIDC_ISSUER"),
67
+ oidc_audience=os.getenv("OIDC_AUDIENCE"),
68
+ )
69
+
70
+
71
+
72
+ def load_role_policies(settings: Settings) -> dict[str, list[str]]:
73
+ raw = _load_yaml(settings.policy_roles_path)
74
+ roles = raw.get("roles", {})
75
+ return {str(k): [str(v) for v in values] for k, values in roles.items()}
76
+
77
+
78
+
79
+ def load_scope_policies(settings: Settings) -> dict[str, list[str]]:
80
+ raw = _load_yaml(settings.policy_scopes_path)
81
+ scopes = raw.get("scopes", {})
82
+ return {str(k): [str(v) for v in values] for k, values in scopes.items()}
package/server/health.py CHANGED
@@ -1,19 +1,19 @@
1
- from __future__ import annotations
2
-
3
- from fastapi import FastAPI
4
- from fastapi.responses import JSONResponse
5
- from mcp.server.fastmcp import FastMCP
6
-
7
- from server.config import load_settings
8
-
9
- settings = load_settings()
10
- mcp = FastMCP(settings.service_name, json_response=True)
11
- app = FastAPI(title="aegis-mcp")
12
-
13
-
14
- @app.get("/health")
15
- def health() -> JSONResponse:
16
- return JSONResponse({"status": "ok", "service": settings.service_name})
17
-
18
-
19
- app.mount("/mcp", mcp.streamable_http_app())
1
+ from __future__ import annotations
2
+
3
+ from fastapi import FastAPI
4
+ from fastapi.responses import JSONResponse
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ from server.config import load_settings
8
+
9
+ settings = load_settings()
10
+ mcp = FastMCP(settings.service_name, json_response=True)
11
+ app = FastAPI(title="aegis-mcp")
12
+
13
+
14
+ @app.get("/health")
15
+ def health() -> JSONResponse:
16
+ return JSONResponse({"status": "ok", "service": settings.service_name})
17
+
18
+
19
+ app.mount("/mcp", mcp.streamable_http_app())
package/server/logging.py CHANGED
@@ -1,33 +1,33 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import logging
5
- import sys
6
- from datetime import datetime, timezone
7
- from typing import Any
8
-
9
-
10
- class JsonFormatter(logging.Formatter):
11
- def format(self, record: logging.LogRecord) -> str:
12
- payload: dict[str, Any] = {
13
- "timestamp": datetime.now(tz=timezone.utc).isoformat(),
14
- "level": record.levelname,
15
- "message": record.getMessage(),
16
- "logger": record.name,
17
- }
18
- extra = getattr(record, "extra_payload", None)
19
- if isinstance(extra, dict):
20
- payload.update(extra)
21
- return json.dumps(payload, default=str)
22
-
23
-
24
- def get_logger(name: str = "mcp.aegis") -> logging.Logger:
25
- logger = logging.getLogger(name)
26
- if logger.handlers:
27
- return logger
28
- logger.setLevel(logging.INFO)
29
- handler = logging.StreamHandler(sys.stderr)
30
- handler.setFormatter(JsonFormatter())
31
- logger.addHandler(handler)
32
- logger.propagate = False
33
- return logger
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import logging
5
+ import sys
6
+ from datetime import datetime, timezone
7
+ from typing import Any
8
+
9
+
10
+ class JsonFormatter(logging.Formatter):
11
+ def format(self, record: logging.LogRecord) -> str:
12
+ payload: dict[str, Any] = {
13
+ "timestamp": datetime.now(tz=timezone.utc).isoformat(),
14
+ "level": record.levelname,
15
+ "message": record.getMessage(),
16
+ "logger": record.name,
17
+ }
18
+ extra = getattr(record, "extra_payload", None)
19
+ if isinstance(extra, dict):
20
+ payload.update(extra)
21
+ return json.dumps(payload, default=str)
22
+
23
+
24
+ def get_logger(name: str = "mcp.aegis") -> logging.Logger:
25
+ logger = logging.getLogger(name)
26
+ if logger.handlers:
27
+ return logger
28
+ logger.setLevel(logging.INFO)
29
+ handler = logging.StreamHandler(sys.stderr)
30
+ handler.setFormatter(JsonFormatter())
31
+ logger.addHandler(handler)
32
+ logger.propagate = False
33
+ return logger