katalyst-taxonomy-mcp 2.4.0__tar.gz

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.
Files changed (33) hide show
  1. katalyst_taxonomy_mcp-2.4.0/.gitignore +105 -0
  2. katalyst_taxonomy_mcp-2.4.0/PKG-INFO +14 -0
  3. katalyst_taxonomy_mcp-2.4.0/pyproject.toml +32 -0
  4. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/__init__.py +11 -0
  5. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/__main__.py +29 -0
  6. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/prompts.py +96 -0
  7. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/py.typed +0 -0
  8. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/resources.py +176 -0
  9. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/response.py +55 -0
  10. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/rest/__init__.py +5 -0
  11. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/rest/__main__.py +103 -0
  12. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/rest/app.py +180 -0
  13. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/rest/manager.py +245 -0
  14. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/rest/routes/__init__.py +5 -0
  15. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/rest/routes/events.py +60 -0
  16. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/rest/routes/taxonomy.py +174 -0
  17. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/rest/state.py +110 -0
  18. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/server.py +131 -0
  19. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/tools/__init__.py +1 -0
  20. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/tools/agents.py +312 -0
  21. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/tools/backends.py +399 -0
  22. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/tools/cicd.py +236 -0
  23. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/tools/engine.py +484 -0
  24. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/tools/hooks.py +149 -0
  25. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/tools/lifecycle.py +236 -0
  26. katalyst_taxonomy_mcp-2.4.0/src/katalyst_taxonomy_mcp/tools/taxonomy.py +619 -0
  27. katalyst_taxonomy_mcp-2.4.0/tests/conftest.py +75 -0
  28. katalyst_taxonomy_mcp-2.4.0/tests/test_backends.py +101 -0
  29. katalyst_taxonomy_mcp-2.4.0/tests/test_engine.py +227 -0
  30. katalyst_taxonomy_mcp-2.4.0/tests/test_resources.py +47 -0
  31. katalyst_taxonomy_mcp-2.4.0/tests/test_rest_app.py +164 -0
  32. katalyst_taxonomy_mcp-2.4.0/tests/test_server.py +89 -0
  33. katalyst_taxonomy_mcp-2.4.0/tests/test_taxonomy.py +104 -0
@@ -0,0 +1,105 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # PyInstaller
28
+ *.manifest
29
+ *.spec
30
+
31
+ # Installer logs
32
+ pip-log.txt
33
+ pip-delete-this-directory.txt
34
+
35
+ # Unit test / coverage reports
36
+ htmlcov/
37
+ .tox/
38
+ .nox/
39
+ .coverage
40
+ .coverage.*
41
+ .cache
42
+ nosetests.xml
43
+ coverage.xml
44
+ *.cover
45
+ *.py,cover
46
+ .hypothesis/
47
+ .pytest_cache/
48
+
49
+ # Translations
50
+ *.mo
51
+ *.pot
52
+
53
+ # Environments
54
+ .env
55
+ .venv
56
+ env/
57
+ venv/
58
+ ENV/
59
+ env.bak/
60
+ venv.bak/
61
+
62
+ # Spyder project settings
63
+ .spyderproject
64
+ .spyproject
65
+
66
+ # Rope project settings
67
+ .ropeproject
68
+
69
+ # mkdocs documentation
70
+ /site
71
+
72
+ # mypy / pyright
73
+ .mypy_cache/
74
+ .dmypy.json
75
+ dmypy.json
76
+ .pyright/
77
+
78
+ # Ruff
79
+ .ruff_cache/
80
+
81
+ # IDE
82
+ .idea/
83
+ .vscode/
84
+ *.swp
85
+ *.swo
86
+ *~
87
+
88
+ # OS
89
+ .DS_Store
90
+ Thumbs.db
91
+
92
+ # Local configuration
93
+ *.local
94
+
95
+ # Browser/Playwright state files
96
+ browser-state-*.json
97
+
98
+ # Build artifacts
99
+ site/
100
+ stack-tests/cucumber-report/
101
+ stack-tests/test-results/
102
+
103
+ # Generated artifacts (rebuild with `just generate-schemas`)
104
+ taxonomy/src/katalyst_taxonomy/engine/generated/
105
+ .worktrees/
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: katalyst-taxonomy-mcp
3
+ Version: 2.4.0
4
+ Summary: MCP server and REST API for katalyst-taxonomy
5
+ Author: eSimplicity
6
+ License: Proprietary
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: fastapi>=0.115
9
+ Requires-Dist: katalyst-engine>=2.2.1
10
+ Requires-Dist: katalyst-taxonomy>=2.2.1
11
+ Requires-Dist: loguru>=0.7
12
+ Requires-Dist: mcp[cli]>=1.0
13
+ Requires-Dist: sse-starlette>=2.0
14
+ Requires-Dist: uvicorn>=0.30
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "katalyst-taxonomy-mcp"
3
+ version = "2.4.0"
4
+ description = "MCP server and REST API for katalyst-taxonomy"
5
+ license = { text = "Proprietary" }
6
+ requires-python = ">=3.12"
7
+ authors = [
8
+ { name = "eSimplicity" }
9
+ ]
10
+ dependencies = [
11
+ "katalyst-taxonomy>=2.2.1",
12
+ "katalyst-engine>=2.2.1",
13
+ "mcp[cli]>=1.0",
14
+ "fastapi>=0.115",
15
+ "uvicorn>=0.30",
16
+ "sse-starlette>=2.0",
17
+ "loguru>=0.7",
18
+ ]
19
+
20
+ [project.scripts]
21
+ katalyst-mcp = "katalyst_taxonomy_mcp.__main__:main"
22
+ katalyst-server = "katalyst_taxonomy_mcp.rest.__main__:main"
23
+
24
+ [build-system]
25
+ requires = ["hatchling"]
26
+ build-backend = "hatchling.build"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["src/katalyst_taxonomy_mcp"]
30
+
31
+ [tool.hatch.build.targets.wheel.sources]
32
+ "src" = ""
@@ -0,0 +1,11 @@
1
+ """MCP (Model Context Protocol) server adapter for katalyst-taxonomy.
2
+
3
+ This package exposes taxonomy operations as MCP tools, resources, and prompts
4
+ so AI coding assistants can discover, query, and manipulate taxonomy data.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from katalyst_taxonomy_mcp.server import create_mcp_server
10
+
11
+ __all__ = ["create_mcp_server"]
@@ -0,0 +1,29 @@
1
+ """Entry point for the MCP server — runs with stdio transport."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+
9
+ def main() -> None:
10
+ """Run the MCP server over stdio."""
11
+ # Accept an optional --path argument
12
+ base_path: Path | None = None
13
+ args = sys.argv[1:]
14
+ i = 0
15
+ while i < len(args):
16
+ if args[i] == "--path" and i + 1 < len(args):
17
+ base_path = Path(args[i + 1])
18
+ i += 2
19
+ else:
20
+ i += 1
21
+
22
+ from katalyst_taxonomy_mcp.server import create_mcp_server
23
+
24
+ mcp = create_mcp_server(base_path=base_path)
25
+ mcp.run(transport="stdio")
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main()
@@ -0,0 +1,96 @@
1
+ """MCP prompt templates for interactive taxonomy operations."""
2
+
3
+ from mcp.server.fastmcp import FastMCP
4
+
5
+
6
+ def register_prompts(mcp: FastMCP) -> None:
7
+ """Register prompt templates on the MCP server."""
8
+
9
+ @mcp.prompt()
10
+ def create_node(node_type: str, name: str) -> str:
11
+ """Interactive node creation wizard with type-specific guidance.
12
+
13
+ Args:
14
+ node_type: The type of taxonomy node to create (e.g., system, stack, layer).
15
+ name: The name for the new node.
16
+ """
17
+ type_guidance = {
18
+ "system": (
19
+ "A system is a top-level organizational unit in the taxonomy. "
20
+ "It groups related stacks and layers that form a coherent platform.\n\n"
21
+ "Required fields:\n"
22
+ "- description: What this system represents\n"
23
+ "- owners: Team email addresses responsible for this system\n"
24
+ "- environments: Which environments this system deploys to (e.g., dev, staging, prod)"
25
+ ),
26
+ "stack": (
27
+ "A stack is a logical grouping of layers within a system. "
28
+ "It represents a deployable unit or service boundary.\n\n"
29
+ "Required fields:\n"
30
+ "- parent: The system this stack belongs to\n"
31
+ "- description: What this stack does\n"
32
+ "- owners: Team email addresses\n"
33
+ "- environments: Deployment environments"
34
+ ),
35
+ "layer": (
36
+ "A layer is the lowest-level building block in the taxonomy. "
37
+ "It represents a specific workload type (Docker app, Terraform infra, etc.).\n\n"
38
+ "Required fields:\n"
39
+ "- parent: The stack this layer belongs to\n"
40
+ "- layer_type: The workload type (e.g., app-docker, infra-terraform)\n"
41
+ "- description: What this layer does\n"
42
+ "- owners: Team email addresses\n"
43
+ "- environments: Deployment environments"
44
+ ),
45
+ }
46
+
47
+ guidance = type_guidance.get(
48
+ node_type,
49
+ f"Creating a '{node_type}' node. Check the registry for required fields.",
50
+ )
51
+
52
+ return (
53
+ f"I need to create a new {node_type} node named '{name}' in the taxonomy.\n\n"
54
+ f"{guidance}\n\n"
55
+ f"Please use the taxonomy_create tool to create this node. "
56
+ f"Ask me for any missing required fields before proceeding."
57
+ )
58
+
59
+ @mcp.prompt()
60
+ def explain_taxonomy() -> str:
61
+ """Explain the taxonomy structure for the current repository.
62
+
63
+ Provides context about what the taxonomy contains and how it's organized.
64
+ """
65
+ return (
66
+ "Please explain the taxonomy structure of this repository. "
67
+ "Start by using the taxonomy_tree tool to show the hierarchy, "
68
+ "then use taxonomy_status to show installed components. "
69
+ "Summarize:\n"
70
+ "1. How many systems, stacks, and layers exist\n"
71
+ "2. What environments are configured\n"
72
+ "3. The overall architecture the taxonomy describes\n"
73
+ "4. Any notable patterns (shared libraries, extensions, etc.)"
74
+ )
75
+
76
+ @mcp.prompt()
77
+ def suggest_layer_type(workload_description: str) -> str:
78
+ """Recommend a layer type based on a workload description.
79
+
80
+ Args:
81
+ workload_description: A description of the workload or service to model.
82
+ """
83
+ return (
84
+ f"Based on this workload description: '{workload_description}'\n\n"
85
+ "Please recommend an appropriate layer type. First, use the taxonomy_status "
86
+ "tool to see what layer types are already installed. Then suggest the best "
87
+ "match, explaining:\n"
88
+ "1. Which layer type fits best and why\n"
89
+ "2. What directory structure it will create\n"
90
+ "3. What actions (build, deploy, test) are available for it\n"
91
+ "4. If no existing type fits, suggest creating a custom one"
92
+ )
93
+
94
+ _ = create_node
95
+ _ = explain_taxonomy
96
+ _ = suggest_layer_type
@@ -0,0 +1,176 @@
1
+ """Read-only MCP resources for taxonomy data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from mcp.server.fastmcp import FastMCP
8
+
9
+ from katalyst_taxonomy_mcp.server import AppContext
10
+
11
+ # Module-level reference set during lifespan startup.
12
+ # Resources can't receive Context params for non-template URIs in FastMCP,
13
+ # so we use this holder as a workaround.
14
+ _app_ctx_holder: list[AppContext] = []
15
+
16
+
17
+ def set_app_ctx(ctx: AppContext) -> None:
18
+ """Store the app context for resource access."""
19
+ _app_ctx_holder.clear()
20
+ _app_ctx_holder.append(ctx)
21
+
22
+
23
+ def _get_app_ctx() -> AppContext | None:
24
+ """Get the stored app context, if any."""
25
+ return _app_ctx_holder[0] if _app_ctx_holder else None
26
+
27
+
28
+ def register_resources(mcp: FastMCP) -> None:
29
+ """Register read-only resources on the MCP server."""
30
+
31
+ @mcp.resource("taxonomy://node/{name}")
32
+ def taxonomy_node_resource(name: str) -> str:
33
+ """Single taxonomy node document as JSON."""
34
+ app = _get_app_ctx()
35
+ if app is None:
36
+ return json.dumps({"error": "Server not initialized."})
37
+
38
+ from katalyst_taxonomy.services.serialization import serialize_document
39
+
40
+ service = app.load_taxonomy()
41
+ doc = service.get_document_by_name(name)
42
+ if doc is None:
43
+ return json.dumps({"error": f"Node '{name}' not found."})
44
+
45
+ payload = serialize_document(doc, service, app.base_path)
46
+ return json.dumps(payload, indent=2)
47
+
48
+ @mcp.resource("taxonomy://tree")
49
+ def taxonomy_tree_resource() -> str:
50
+ """Full taxonomy tree in text format."""
51
+ app = _get_app_ctx()
52
+ if app is None:
53
+ return "Server not initialized."
54
+
55
+ from katalyst_taxonomy.services.serialization import build_tree_lines
56
+
57
+ service = app.load_taxonomy()
58
+ if not service.documents:
59
+ return "No taxonomy documents found."
60
+
61
+ return "\n".join(build_tree_lines(service))
62
+
63
+ @mcp.resource("taxonomy://environments")
64
+ def taxonomy_environments_resource() -> str:
65
+ """Environment configurations as JSON."""
66
+ app = _get_app_ctx()
67
+ if app is None:
68
+ return json.dumps([])
69
+
70
+ from katalyst_taxonomy.services.serialization import serialize_document
71
+ from katalyst_taxonomy.services.taxonomy import TaxonomyService
72
+
73
+ repo = app.backend
74
+ documents = repo.load_documents()
75
+ env_documents = [d for d in documents if d.kind == "environment"]
76
+ service = TaxonomyService(documents=tuple(documents))
77
+
78
+ data = [serialize_document(env, service) for env in env_documents]
79
+ return json.dumps(data, indent=2)
80
+
81
+ @mcp.resource("taxonomy://registry")
82
+ def taxonomy_registry_resource() -> str:
83
+ """Node type registry metadata as JSON."""
84
+ from katalyst_taxonomy.engine.bridge import get_default_registry
85
+
86
+ registry = get_default_registry()
87
+ data: list[dict[str, object]] = []
88
+ for kind in registry.all_kinds():
89
+ desc = registry.get_node_definition(kind)
90
+ if desc is None:
91
+ continue
92
+ data.append(
93
+ {
94
+ "kind": desc.describes_kind,
95
+ "wing": desc.wing,
96
+ "displayOrder": desc.display_order,
97
+ "directoryName": desc.directory_name,
98
+ "parentTypes": list(desc.allowed_parent_kinds)
99
+ if desc.allowed_parent_kinds
100
+ else None,
101
+ "isLeaf": desc.is_leaf,
102
+ "isRoot": desc.is_root,
103
+ "isExtension": desc.is_extension,
104
+ }
105
+ )
106
+
107
+ return json.dumps(data, indent=2)
108
+
109
+ @mcp.resource("taxonomy://status")
110
+ def taxonomy_status_resource() -> str:
111
+ """Installed components and versions."""
112
+ app = _get_app_ctx()
113
+ if app is None:
114
+ return "Server not initialized."
115
+
116
+ from katalyst_taxonomy.adapters.filesystem.lock import LockFileRepository
117
+ from katalyst_taxonomy.services.status import StatusCode, get_status
118
+
119
+ result = get_status(app.base_path, lock_repo=LockFileRepository(base_path=app.base_path))
120
+
121
+ if result.status == StatusCode.NOT_INITIALIZED:
122
+ return f"No Katalyst lock file found.\nExpected: {result.lock_file_path}"
123
+
124
+ if result.status == StatusCode.ERROR:
125
+ return f"Error reading lock file: {result.error}"
126
+
127
+ lock = result.lock
128
+ assert lock is not None
129
+ return (
130
+ f"Katalyst Taxonomy Status\n"
131
+ f" Version: {lock.installed.core.version}\n"
132
+ f" Schema: {lock.installed.core.schema_version}\n"
133
+ f" Source: {lock.installed.core.source}"
134
+ )
135
+
136
+ @mcp.resource("taxonomy://agents")
137
+ def taxonomy_agents_resource() -> str:
138
+ """Agent definitions list as JSON."""
139
+ app = _get_app_ctx()
140
+ if app is None:
141
+ return json.dumps({"agents": [], "skills": [], "error": "Server not initialized."})
142
+
143
+ from katalyst_taxonomy.agents.discovery import (
144
+ discover_agents_directory,
145
+ scan_agents_directory,
146
+ )
147
+
148
+ agents_dir = discover_agents_directory(app.base_path)
149
+ if not agents_dir:
150
+ return json.dumps({"agents": [], "skills": [], "error": "No .agents/ directory found."})
151
+
152
+ directory = scan_agents_directory(agents_dir)
153
+ data = {
154
+ "path": str(directory.path),
155
+ "agents": [
156
+ {
157
+ "name": a.name,
158
+ "description": a.description,
159
+ "mode": a.mode,
160
+ "source": a.source,
161
+ }
162
+ for a in directory.agents
163
+ ],
164
+ "skills": [
165
+ {"name": s.name, "description": s.description, "source": s.source}
166
+ for s in directory.skills
167
+ ],
168
+ }
169
+ return json.dumps(data, indent=2)
170
+
171
+ _ = taxonomy_node_resource
172
+ _ = taxonomy_tree_resource
173
+ _ = taxonomy_environments_resource
174
+ _ = taxonomy_registry_resource
175
+ _ = taxonomy_status_resource
176
+ _ = taxonomy_agents_resource
@@ -0,0 +1,55 @@
1
+ """Structured response helpers for MCP tools and REST API.
2
+
3
+ All MCP tools and REST endpoints use these helpers to ensure consistent
4
+ response shapes: {ok: true, data: ...} or {ok: false, error: {code, message, details}}.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from enum import StrEnum
10
+ from typing import Any
11
+
12
+
13
+ class ErrorCode(StrEnum):
14
+ """Standardized error codes for structured responses."""
15
+
16
+ NOT_FOUND = "NOT_FOUND"
17
+ VALIDATION_ERROR = "VALIDATION_ERROR"
18
+ DUPLICATE = "DUPLICATE"
19
+ IO_ERROR = "IO_ERROR"
20
+ INVALID_INPUT = "INVALID_INPUT"
21
+ INTERNAL_ERROR = "INTERNAL_ERROR"
22
+ ALREADY_EXISTS = "ALREADY_EXISTS"
23
+ NOT_INITIALIZED = "NOT_INITIALIZED"
24
+ PERMISSION_DENIED = "PERMISSION_DENIED"
25
+
26
+
27
+ def ok(data: Any) -> dict[str, Any]:
28
+ """Return a successful structured response.
29
+
30
+ Args:
31
+ data: The response payload. Can be any JSON-serializable value.
32
+
33
+ Returns:
34
+ {"ok": True, "data": data}
35
+ """
36
+ return {"ok": True, "data": data}
37
+
38
+
39
+ def err(
40
+ code: str | ErrorCode, message: str, details: dict[str, Any] | None = None
41
+ ) -> dict[str, Any]:
42
+ """Return a structured error response.
43
+
44
+ Args:
45
+ code: Error code from ErrorCode enum or a string.
46
+ message: Human-readable error message.
47
+ details: Optional additional context about the error.
48
+
49
+ Returns:
50
+ {"ok": False, "error": {"code": ..., "message": ..., "details": ...}}
51
+ """
52
+ error: dict[str, Any] = {"code": str(code), "message": message}
53
+ if details:
54
+ error["details"] = details
55
+ return {"ok": False, "error": error}
@@ -0,0 +1,5 @@
1
+ """Katalyst Server - FastAPI HTTP server for taxonomy operations."""
2
+
3
+ from katalyst_taxonomy_mcp.rest.app import create_app
4
+
5
+ __all__ = ["create_app"]
@@ -0,0 +1,103 @@
1
+ """Entry point for running the server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ from pathlib import Path
8
+
9
+ import uvicorn
10
+ from loguru import logger
11
+
12
+ from katalyst_taxonomy_mcp.rest.app import create_app
13
+ from katalyst_taxonomy_mcp.rest.state import ServerState, save_server_state
14
+
15
+ DEFAULT_IDLE_TIMEOUT = 300 # 5 minutes
16
+
17
+
18
+ def main() -> None:
19
+ """Run the Katalyst server."""
20
+ parser = argparse.ArgumentParser(description="Katalyst Taxonomy Server")
21
+ parser.add_argument(
22
+ "--path",
23
+ type=Path,
24
+ default=Path.cwd(),
25
+ help="Path to taxonomy directory (default: current directory)",
26
+ )
27
+ parser.add_argument(
28
+ "--host",
29
+ type=str,
30
+ default="127.0.0.1",
31
+ help="Host to bind to (default: 127.0.0.1)",
32
+ )
33
+ parser.add_argument(
34
+ "--port",
35
+ type=int,
36
+ default=8000,
37
+ help="Port to bind to (default: 8000)",
38
+ )
39
+ parser.add_argument(
40
+ "--reload",
41
+ action="store_true",
42
+ help="Enable auto-reload for development",
43
+ )
44
+ parser.add_argument(
45
+ "--idle-timeout",
46
+ type=int,
47
+ default=DEFAULT_IDLE_TIMEOUT,
48
+ help=f"Idle timeout in seconds before auto-shutdown (default: {DEFAULT_IDLE_TIMEOUT}, 0 to disable)",
49
+ )
50
+ parser.add_argument(
51
+ "--background",
52
+ action="store_true",
53
+ help="Run in background mode (used by server manager)",
54
+ )
55
+
56
+ args = parser.parse_args()
57
+
58
+ resolved_path = args.path.resolve()
59
+
60
+ # Save server state for the manager to track
61
+ state = ServerState.create(
62
+ pid=os.getpid(),
63
+ port=args.port,
64
+ host=args.host,
65
+ path=resolved_path,
66
+ )
67
+ save_server_state(state)
68
+
69
+ if args.background:
70
+ # Suppress uvicorn logging in background mode
71
+ log_config = {
72
+ "version": 1,
73
+ "disable_existing_loggers": True,
74
+ "handlers": {},
75
+ "loggers": {"uvicorn": {"level": "WARNING"}, "uvicorn.error": {"level": "WARNING"}},
76
+ }
77
+ else:
78
+ log_config = None
79
+ logger.info(f"Starting Katalyst server on {args.host}:{args.port}")
80
+ logger.info(f"Taxonomy path: {resolved_path}")
81
+ if args.idle_timeout > 0:
82
+ logger.info(f"Idle timeout: {args.idle_timeout}s")
83
+ else:
84
+ logger.info("Idle timeout: disabled")
85
+
86
+ app = create_app(base_path=resolved_path, idle_timeout=args.idle_timeout)
87
+
88
+ uvicorn_kwargs: dict[str, object] = {
89
+ "host": args.host,
90
+ "port": args.port,
91
+ }
92
+
93
+ if log_config is not None:
94
+ uvicorn_kwargs["log_config"] = log_config
95
+
96
+ if args.reload:
97
+ uvicorn_kwargs["reload"] = True
98
+
99
+ uvicorn.run(app, **uvicorn_kwargs) # type: ignore[arg-type]
100
+
101
+
102
+ if __name__ == "__main__":
103
+ main()