CI / test (push) Canceled after 0s
## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.com>
87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
"""Fail-closed identity, policy, and provider resolution for the template server."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from .foundation import IdentityContext, ProjectContextRuntime, ProviderError
|
|
from .providers.change import build_provider as build_change_provider
|
|
from .providers.issue import build_provider as build_issue_provider
|
|
from .providers.knowledge import build_provider as build_knowledge_provider
|
|
|
|
MINIMUM_PYTHON = (3, 11)
|
|
|
|
|
|
def require_supported_python(version_info: tuple[int, ...] | None = None) -> None:
|
|
"""Fail with an actionable message before the MCP server starts."""
|
|
current = version_info or tuple(sys.version_info[:3])
|
|
if current[:2] < MINIMUM_PYTHON:
|
|
raise RuntimeError(
|
|
"Project Context MCP requires Python 3.11 or newer; "
|
|
f"current runtime is {current[0]}.{current[1]}"
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProjectScopePolicy:
|
|
"""Pilot policy: read scope and exact identity-bound project are both mandatory."""
|
|
|
|
def decide(self, identity: IdentityContext, tool_name: str, project_id: str) -> bool:
|
|
"""Chỉ cho phép khi danh tính có phạm vi ``read`` VÀ project khớp đúng
|
|
project gắn với danh tính đó — không cho đọc chéo project.
|
|
"""
|
|
return "read" in identity.granted_scopes and project_id == identity.project
|
|
|
|
|
|
PROVIDER_FACTORIES: dict[str, Callable[[IdentityContext], Any]] = {
|
|
"get_project_issue_context": build_issue_provider,
|
|
"search_project_knowledge": build_knowledge_provider,
|
|
"get_project_change_context": build_change_provider,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProjectProviderResolver:
|
|
"""Tra provider thật cho từng tool theo bảng ``PROVIDER_FACTORIES``."""
|
|
def resolve(self, identity: IdentityContext, tool_name: str) -> Any:
|
|
"""Dựng provider cho một tool; tool chưa đăng ký thì báo ``NOT_FOUND`` và
|
|
không cho thử lại.
|
|
"""
|
|
factory = PROVIDER_FACTORIES.get(tool_name)
|
|
if factory is None:
|
|
raise ProviderError("NOT_FOUND", "The requested tool is not registered.", retryable=False)
|
|
return factory(identity)
|
|
|
|
|
|
def _required_environment(name: str) -> str:
|
|
"""Đọc một biến môi trường bắt buộc; thiếu thì dừng ngay lúc khởi động.
|
|
|
|
Thà không chạy còn hơn chạy với cấu hình khuyết rồi lỗi giữa chừng ở một
|
|
lượt gọi tool nào đó.
|
|
"""
|
|
value = os.environ.get(name, "").strip()
|
|
if not value:
|
|
raise RuntimeError(f"Project Context MCP cannot start: required setting {name} is missing")
|
|
return value
|
|
|
|
|
|
def default_runtime() -> ProjectContextRuntime:
|
|
"""Build immutable runtime state; missing identity configuration fails at boot."""
|
|
require_supported_python()
|
|
identity = IdentityContext(
|
|
actor_id=_required_environment("COWORK_MCP_ACTOR_ID"),
|
|
org_unit=_required_environment("COWORK_MCP_ORG_UNIT"),
|
|
customer=_required_environment("COWORK_MCP_CUSTOMER"),
|
|
project=_required_environment("COWORK_MCP_PROJECT"),
|
|
granted_scopes=frozenset({"read"}),
|
|
)
|
|
return ProjectContextRuntime(
|
|
identity=identity,
|
|
policy=ProjectScopePolicy(),
|
|
credential_resolver=ProjectProviderResolver(),
|
|
)
|