# Coding Standards & Conventions: SSO MCP Server
**Version**: 1.1 | **Date**: 2025-12-15 | **Status**: Active
**Maintained by**: Development Team | **Last Reviewed**: 2025-12-15
**Note**: This document defines the coding standards, naming conventions, and best practices for the entire product. All developers must follow these standards to ensure consistency, maintainability, and quality across the codebase.
---
## Document Control
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | 2025-12-11 | Development Team | Initial standards document |
| 1.1 | 2025-12-15 | Development Team | Update title to reflect multi-function server |
**Related Documents**:
- Architecture: `docs/architecture.md`
- Ground Rules: `memory/ground-rules.md`
- Feature Specifications:
- `specs/001-mcp-sso-checklist/spec.md` - Checklist feature
- `specs/003-process-query/spec.md` - Process Query feature
---
## Table of Contents
1. [Introduction](#1-introduction)
2. [UI Naming Conventions](#2-ui-naming-conventions)
3. [Code Naming Conventions](#3-code-naming-conventions)
4. [File and Directory Structure](#4-file-and-directory-structure)
5. [MCP Tool Design Standards](#5-mcp-tool-design-standards)
6. [Database Standards](#6-database-standards)
7. [Testing Standards](#7-testing-standards)
8. [Git Workflow](#8-git-workflow)
9. [Documentation Standards](#9-documentation-standards)
10. [Code Style Guide](#10-code-style-guide)
11. [Enforcement](#11-enforcement)
12. [Appendices](#12-appendices)
---
## 1. Introduction
### 1.1 Purpose
This document establishes comprehensive coding standards and naming conventions for the SSO MCP Checklist Server. Following these standards ensures:
- **Consistency**: Code looks uniform across the codebase
- **Maintainability**: Code is easier to understand and modify
- **Collaboration**: Team members can read and work with each other's code
- **Quality**: Automated tools can enforce standards
- **Onboarding**: New team members can quickly understand conventions
### 1.2 Scope
These standards apply to:
- All source code in the repository
- All documentation
- All configuration files
- All MCP tool definitions
- All test code
### 1.3 Technology Stack
**Backend**: Python 3.11+
**MCP Framework**: mcp ^1.23.3 (FastMCP)
**Authentication**: msal ^1.30.0, msal-extensions ^1.2.0
**Configuration**: python-dotenv ^1.0.0
**Checklist Parsing**: python-frontmatter ^1.1.0
**Logging**: structlog ^24.0.0
**Testing**: pytest ^8.0.0, pytest-asyncio ^0.23.0
**Linting/Formatting**: ruff ^0.5.0
**Security Scanning**: bandit ^1.7.0
### 1.4 How to Use This Document
- **Developers**: Follow these standards in all code you write
- **Code Reviewers**: Verify adherence to these standards in PRs
- **Team Leads**: Enforce standards and update this document as needed
- **New Team Members**: Read this document during onboarding
---
## 2. UI Naming Conventions
**N/A - No UI Layer**
This project is a backend-only CLI application implementing an MCP server. There are no frontend components, web interfaces, or graphical user interfaces.
**Project Type**: Command-line MCP server application
**User Interaction**: AI assistants (GitHub Copilot, Claude Code) interact via MCP protocol
**Configuration**: Environment variables and JSON configuration files
If UI components are added in the future, this section should be updated following React/TypeScript conventions.
---
## 3. Code Naming Conventions
### 3.1 Variables
#### 3.1.1 Local Variables
**Convention**: snake_case, descriptive nouns or noun phrases
```python
# Good - snake_case, descriptive
user_name = "John"
total_amount = 100.50
is_authenticated = True
checklist_items = []
current_token = None
access_token_expiry = datetime.now()
# Bad - unclear, abbreviated, wrong case
usr = "John"
amt = 100.50
auth = True
lst = []
tok = None
accessTokenExpiry = datetime.now() # Wrong case
```
#### 3.1.2 Constants
**Convention**: SCREAMING_SNAKE_CASE
```python
# Good - SCREAMING_SNAKE_CASE
MAX_RETRY_COUNT = 3
DEFAULT_TIMEOUT_SECONDS = 30
TOKEN_REFRESH_THRESHOLD_MINUTES = 5
DEFAULT_MCP_PORT = 8080
TOKEN_CACHE_FILENAME = "token_cache.bin"
CHECKLIST_FILE_EXTENSION = ".md"
# Bad - inconsistent casing
maxRetryCount = 3
Max_Retry_Count = 3
MAXRETRYCOUNT = 3
default_timeout = 30 # Should be SCREAMING_SNAKE_CASE
```
#### 3.1.3 Module-Level Variables
**Convention**: Leading underscore for internal use, SCREAMING_SNAKE_CASE for public globals
```python
# Good - module-level conventions
_INTERNAL_CACHE: dict[str, Any] = {} # Leading underscore for internal
_logger = structlog.get_logger() # Internal logger instance
# Public module constants
DEFAULT_CONFIG = {
"port": 8080,
"log_level": "INFO"
}
```
### 3.2 Functions and Methods
#### 3.2.1 Function Names
**Convention**: snake_case, verb-based, descriptive
```python
# Good - verb + noun, descriptive
def calculate_token_expiry(token: dict) -> datetime:
pass
def fetch_checklist_content(name: str) -> str:
pass
def validate_configuration(config: dict) -> bool:
pass
def format_error_response(error: Exception) -> dict:
pass
def parse_frontmatter(content: str) -> tuple[dict, str]:
pass
# Bad - noun-based, unclear, too generic
def total(items): # Missing verb
def checklist(name): # Not descriptive
def check(config): # Too generic
def do_it(): # Meaningless
```
#### 3.2.2 Boolean Functions
**Convention**: Prefix with `is_`, `has_`, `should_`, `can_`
```python
# Good - boolean intent clear
def is_authenticated(manager: AuthManager) -> bool:
return manager.token is not None
def has_valid_token(token: dict) -> bool:
return token.get("expires_at", 0) > time.time()
def should_refresh_token(token: dict, threshold_minutes: int = 5) -> bool:
expiry = token.get("expires_at", 0)
return (expiry - time.time()) < (threshold_minutes * 60)
def can_access_checklist(user: str, checklist: str) -> bool:
return True # All authenticated users can access
# Bad - unclear return type
def valid(token): # Could return anything
def token_expired(token): # Not clearly boolean (use is_token_expired)
def refresh(token): # Unclear intent
```
#### 3.2.3 Async Functions
**Convention**: Same as sync functions, use `async def`
```python
# Good - async functions follow same conventions
async def fetch_user_profile(access_token: str) -> dict:
pass
async def get_checklist(name: str) -> ChecklistResponse:
pass
async def list_checklists() -> ListChecklistsResponse:
pass
# Internal async helpers
async def _refresh_token_if_needed(manager: AuthManager) -> None:
pass
```
#### 3.2.4 Private Functions
**Convention**: Leading underscore for internal/private functions
```python
# Good - private function naming
def _validate_port_range(port: int) -> None:
"""Internal validation helper."""
if not 1024 <= port <= 65535:
raise ValueError(f"Port must be between 1024 and 65535, got {port}")
def _format_log_context(user: str, action: str) -> dict:
"""Internal logging helper."""
return {"user": user, "action": action, "timestamp": datetime.now()}
# Public API
def validate_configuration(config: dict) -> None:
"""Public configuration validation."""
_validate_port_range(config.get("port", 8080))
```
### 3.3 Classes and Types
#### 3.3.1 Class Names
**Convention**: PascalCase, nouns
```python
# Good - PascalCase, descriptive nouns
class AuthManager:
"""Manages authentication state and token lifecycle."""
pass
class TokenStore:
"""Handles secure token persistence."""
pass
class ChecklistService:
"""Business logic for checklist operations."""
pass
class FrontmatterParser:
"""Parses YAML frontmatter from markdown files."""
pass
class ServerConfiguration:
"""Holds server configuration settings."""
pass
# Bad - wrong case, verbs, abbreviations
class authManager: # Wrong case
class ProcessAuth: # Verb-based
class TokStore: # Abbreviation
class svc: # Too short
```
#### 3.3.2 Exception Classes
**Convention**: PascalCase with `Error` suffix
```python
# Good - clear exception naming
class AuthenticationError(Exception):
"""Raised when authentication fails."""
pass
class TokenExpiredError(AuthenticationError):
"""Raised when the access token has expired."""
pass
class ChecklistNotFoundError(Exception):
"""Raised when a requested checklist doesn't exist."""
pass
class ConfigurationError(Exception):
"""Raised when configuration is invalid."""
pass
class FileReadError(Exception):
"""Raised when a checklist file cannot be read."""
pass
```
#### 3.3.3 Type Aliases and TypedDicts
**Convention**: PascalCase, descriptive
```python
from typing import TypedDict, TypeAlias
# Type aliases
ChecklistName: TypeAlias = str
AccessToken: TypeAlias = str
FilePath: TypeAlias = str
# TypedDict for structured data
class ChecklistMetadata(TypedDict):
name: str
description: str | None
class ChecklistResponse(TypedDict):
name: str
description: str | None
content: str
class ListChecklistsResponse(TypedDict):
checklists: list[ChecklistMetadata]
count: int
class TokenData(TypedDict):
access_token: str
refresh_token: str | None
expires_at: float
```
#### 3.3.4 Enums
**Convention**: PascalCase for enum, SCREAMING_SNAKE_CASE for values
```python
from enum import Enum, auto
class LogLevel(Enum):
DEBUG = "DEBUG"
INFO = "INFO"
WARNING = "WARNING"
ERROR = "ERROR"
class AuthState(Enum):
NOT_AUTHENTICATED = auto()
AUTHENTICATED = auto()
TOKEN_EXPIRED = auto()
REFRESHING = auto()
class ErrorCode(Enum):
NOT_AUTHENTICATED = "NOT_AUTHENTICATED"
CHECKLIST_NOT_FOUND = "CHECKLIST_NOT_FOUND"
FILE_READ_ERROR = "FILE_READ_ERROR"
DIRECTORY_READ_ERROR = "DIRECTORY_READ_ERROR"
CONFIGURATION_ERROR = "CONFIGURATION_ERROR"
```
### 3.4 Modules and Packages
**Convention**: lowercase with underscores
```python
# Good - lowercase with underscores
# File names
auth_manager.py
token_store.py
checklist_service.py
frontmatter_parser.py
file_discovery.py
# Package names
sso_mcp_server/
auth/
checklists/
config/
tools/
```
---
## 4. File and Directory Structure
### 4.1 File Naming
#### 4.1.1 Source Code Files
**Convention**: snake_case.py
```
# Good - snake_case
server.py
auth_manager.py
token_store.py
browser_auth.py
checklist_service.py
frontmatter_parser.py
file_discovery.py
settings.py
get_checklist.py
list_checklists.py
```
#### 4.1.2 Test Files
**Convention**: `test_<module_name>.py`
```
# Good - test_ prefix
test_auth_manager.py
test_token_store.py
test_checklist_service.py
test_frontmatter_parser.py
test_file_discovery.py
test_mcp_tools.py
test_auth_flow.py
```
#### 4.1.3 Configuration Files
```
# Root level configuration
.env # Environment variables (not committed)
.env.example # Example environment template
pyproject.toml # Project configuration and dependencies
ruff.toml # Ruff linter configuration (if not in pyproject.toml)
.pre-commit-config.yaml # Pre-commit hooks
.editorconfig # Editor configuration
```
### 4.2 Directory Structure
**Per architecture.md §6.1:**
```
sso-mcp-server/
├── src/
│ └── sso_mcp_server/
│ ├── __init__.py # Package init with logging setup
│ ├── __main__.py # CLI entry point
│ ├── server.py # MCP Server Core (FastMCP, HTTP Streamable)
│ ├── config/
│ │ ├── __init__.py
│ │ └── settings.py # Environment configuration
│ ├── auth/
│ │ ├── __init__.py # Auth module exports
│ │ ├── manager.py # Auth Manager (orchestration + token refresh)
│ │ ├── browser.py # Browser Auth (PKCE flow)
│ │ ├── token_store.py # Token persistence (msal-extensions)
│ │ └── middleware.py # Auth middleware for tool calls
│ ├── checklists/
│ │ ├── __init__.py # Checklist module exports
│ │ ├── service.py # Checklist business logic
│ │ ├── discovery.py # File discovery (glob)
│ │ └── parser.py # Frontmatter parser
│ └── tools/
│ ├── __init__.py # Tool registration
│ ├── get_checklist.py # get_checklist tool
│ └── list_checklists.py # list_checklists tool
├── checklists/ # Default checklist directory
│ ├── coding.md
│ ├── architecture.md
│ └── detailed-design.md
├── tests/
│ ├── conftest.py # Shared fixtures
│ ├── unit/
│ │ ├── test_auth_manager.py
│ │ ├── test_token_store.py
│ │ ├── test_checklist_service.py
│ │ ├── test_frontmatter_parser.py
│ │ └── test_file_discovery.py
│ └── integration/
│ ├── test_auth_flow.py
│ └── test_mcp_tools.py
├── docs/
│ ├── architecture.md
│ ├── standards.md # This file
│ └── adr/ # Architecture Decision Records
├── specs/
│ └── 001-mcp-sso-checklist/
│ ├── spec.md
│ ├── design.md
│ └── tasks.md
├── memory/
│ └── ground-rules.md
├── .env.example
├── pyproject.toml
├── README.md
└── CLAUDE.md
```
### 4.3 Module Organization
**Package `__init__.py` conventions:**
```python
# src/sso_mcp_server/auth/__init__.py
"""Authentication module for SSO MCP Server."""
from .manager import AuthManager
from .token_store import TokenStore
from .browser import BrowserAuth
from .middleware import auth_middleware
__all__ = [
"AuthManager",
"TokenStore",
"BrowserAuth",
"auth_middleware",
]
```
---
## 5. MCP Tool Design Standards
This project implements MCP (Model Context Protocol) tools, not REST APIs. This section defines standards for MCP tool design.
### 5.1 Tool Naming
**Convention**: snake_case, verb-noun pattern
```python
# Good - MCP tool names
@mcp.tool()
async def get_checklist(name: str) -> ChecklistResponse:
"""Retrieve a specific checklist by name."""
pass
@mcp.tool()
async def list_checklists() -> ListChecklistsResponse:
"""List all available checklists."""
pass
# Bad - wrong conventions
@mcp.tool()
async def GetChecklist(name: str): # Wrong case
pass
@mcp.tool()
async def checklist(name: str): # Missing verb
pass
```
### 5.2 Tool Input/Output Schemas
**Convention**: Use TypedDict or dataclasses with JSON schema annotations
```python
from typing import TypedDict
# Input schemas (when tool has parameters)
class GetChecklistInput(TypedDict):
name: str # Required: Name of the checklist to retrieve
# Output schemas
class ChecklistResponse(TypedDict):
name: str
description: str | None
content: str
class ChecklistMetadata(TypedDict):
name: str
description: str | None
class ListChecklistsResponse(TypedDict):
checklists: list[ChecklistMetadata]
count: int
```
### 5.3 Tool Documentation
**Convention**: Docstrings with clear descriptions
```python
@mcp.tool()
async def get_checklist(name: str) -> ChecklistResponse:
"""Retrieve a specific checklist by name.
Args:
name: The name of the checklist to retrieve (e.g., "coding", "architecture").
This corresponds to the markdown filename without extension.
Returns:
ChecklistResponse containing:
- name: The checklist name
- description: Optional description from YAML frontmatter
- content: The full markdown content of the checklist
Raises:
McpError: With code CHECKLIST_NOT_FOUND if checklist doesn't exist
McpError: With code NOT_AUTHENTICATED if user is not authenticated
McpError: With code FILE_READ_ERROR if file cannot be read
"""
pass
```
### 5.4 Error Responses
**Convention**: Use MCP error codes with descriptive messages
```python
from mcp.server.fastmcp import FastMCP
from mcp import McpError, ErrorCode
# Standard error codes for this project
ERROR_CODES = {
"NOT_AUTHENTICATED": "User is not authenticated. Please authenticate first.",
"CHECKLIST_NOT_FOUND": "Checklist '{name}' not found. Available: {available}",
"FILE_READ_ERROR": "Failed to read checklist file: {error}",
"DIRECTORY_READ_ERROR": "Failed to read checklist directory: {error}",
}
# Error handling in tools
@mcp.tool()
async def get_checklist(name: str) -> ChecklistResponse:
if not auth_manager.is_authenticated():
raise McpError(
ErrorCode.UNAUTHORIZED,
ERROR_CODES["NOT_AUTHENTICATED"]
)
checklist = checklist_service.get(name)
if checklist is None:
available = ", ".join(checklist_service.list_names())
raise McpError(
ErrorCode.NOT_FOUND,
ERROR_CODES["CHECKLIST_NOT_FOUND"].format(name=name, available=available)
)
return checklist
```
### 5.5 Tool Contract JSON Schema
**Per `specs/001-mcp-sso-checklist/contracts/mcp-tools.json`:**
```json
{
"tools": [
{
"name": "get_checklist",
"description": "Retrieve a specific checklist by name",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the checklist to retrieve"
}
},
"required": ["name"]
}
},
{
"name": "list_checklists",
"description": "List all available checklists",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
}
}
]
}
```
---
## 6. Database Standards
**N/A - No Database**
This project uses local file system storage for checklists and token persistence:
- **Checklists**: Markdown files (`.md`) in configurable directory
- **Token Cache**: Encrypted binary file (`~/.sso-mcp-server/token_cache.bin`)
- **Configuration**: Environment variables
**File Storage Conventions:**
```python
# Checklist files - YAML frontmatter + markdown
"""
---
name: Coding Standards Checklist
description: Quality checklist for code implementation
---
# Coding Standards Checklist
## Pre-Implementation
- [ ] Requirements understood
- [ ] Design reviewed
...
"""
# Path conventions
CHECKLIST_DIR = os.environ.get("CHECKLIST_DIR", "./checklists")
TOKEN_CACHE_DIR = Path.home() / ".sso-mcp-server"
TOKEN_CACHE_FILE = TOKEN_CACHE_DIR / "token_cache.bin"
```
---
## 7. Testing Standards
### 7.1 Test File Organization
```
tests/
├── conftest.py # Shared fixtures
├── unit/ # Unit tests (isolated, fast)
│ ├── test_auth_manager.py
│ ├── test_token_store.py
│ ├── test_checklist_service.py
│ ├── test_frontmatter_parser.py
│ └── test_file_discovery.py
└── integration/ # Integration tests (with dependencies)
├── test_auth_flow.py
└── test_mcp_tools.py
```
### 7.2 Test Naming
**Convention**: `test_<what>_<condition>_<expected>`
```python
# Good - descriptive test names
def test_get_checklist_with_valid_name_returns_content():
pass
def test_get_checklist_with_nonexistent_name_raises_not_found():
pass
def test_auth_manager_refresh_when_token_expires_soon_succeeds():
pass
def test_frontmatter_parser_with_missing_frontmatter_returns_empty_metadata():
pass
def test_list_checklists_when_directory_empty_returns_empty_list():
pass
# Bad - unclear test names
def test_checklist_1():
pass
def test_get():
pass
def test_auth():
pass
```
### 7.3 Test Structure (AAA Pattern)
**Convention**: Arrange-Act-Assert with clear sections
```python
def test_checklist_service_get_with_valid_name_returns_content():
# Arrange
service = ChecklistService(checklist_dir="/tmp/test_checklists")
checklist_path = Path("/tmp/test_checklists/coding.md")
checklist_path.write_text("""---
name: Coding
description: Coding standards
---
# Coding Checklist
- Item 1
""")
# Act
result = service.get("coding")
# Assert
assert result is not None
assert result["name"] == "coding"
assert result["description"] == "Coding standards"
assert "# Coding Checklist" in result["content"]
```
### 7.4 Fixtures
**Convention**: Descriptive names, scoped appropriately
```python
# conftest.py
import pytest
from pathlib import Path
import tempfile
@pytest.fixture
def temp_checklist_dir():
"""Create a temporary directory for checklist files."""
with tempfile.TemporaryDirectory() as tmpdir:
yield Path(tmpdir)
@pytest.fixture
def sample_checklist_content():
"""Return sample checklist content with frontmatter."""
return """---
name: Test Checklist
description: A test checklist for unit tests
---
# Test Checklist
## Section 1
- [ ] Item 1
- [ ] Item 2
"""
@pytest.fixture
def checklist_service(temp_checklist_dir):
"""Create a ChecklistService with temp directory."""
return ChecklistService(checklist_dir=str(temp_checklist_dir))
@pytest.fixture
def mock_msal_app(mocker):
"""Mock MSAL PublicClientApplication."""
mock_app = mocker.MagicMock()
mock_app.acquire_token_interactive.return_value = {
"access_token": "test_access_token",
"refresh_token": "test_refresh_token",
"expires_in": 3600,
}
return mock_app
```
### 7.5 Async Test Conventions
**Convention**: Use pytest-asyncio with `@pytest.mark.asyncio`
```python
import pytest
@pytest.mark.asyncio
async def test_get_checklist_tool_returns_content():
# Arrange
mcp_server = create_test_server()
# Act
result = await mcp_server.call_tool("get_checklist", {"name": "coding"})
# Assert
assert result["name"] == "coding"
assert "content" in result
@pytest.mark.asyncio
async def test_auth_manager_ensure_authenticated_opens_browser():
# Arrange
auth_manager = AuthManager(client_id="test", tenant_id="test")
# Act & Assert
with pytest.raises(AuthenticationError):
await auth_manager.ensure_authenticated()
```
### 7.6 Coverage Requirements
**Per ground-rules.md and architecture.md:**
- **Minimum Coverage**: 80% for critical paths (auth, MCP modules)
- **Critical Paths**:
- `auth/manager.py` - Authentication orchestration
- `auth/token_store.py` - Token persistence
- `checklists/service.py` - Checklist operations
- `tools/get_checklist.py` - MCP tool implementation
- `tools/list_checklists.py` - MCP tool implementation
```bash
# Run tests with coverage
pytest --cov=src/sso_mcp_server --cov-report=term-missing --cov-fail-under=80
```
---
## 8. Git Workflow
### 8.1 Branch Naming
**Convention**: `{type}/{issue-number}-{brief-description}`
```bash
# Good - structured branch names
feature/001-mcp-sso-checklist
feature/002-add-rate-limiting
bugfix/003-fix-token-refresh
hotfix/004-security-patch
chore/005-update-dependencies
docs/006-update-readme
# Bad - unclear branches
john-branch
fix-bug
new-feature
wip
```
**Branch Types**:
- `feature/` - New features
- `bugfix/` - Bug fixes
- `hotfix/` - Critical production fixes
- `chore/` - Maintenance tasks
- `docs/` - Documentation updates
- `refactor/` - Code refactoring
### 8.2 Commit Messages
**Convention**: Conventional Commits format
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
**Types**:
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation changes
- `style`: Code style changes (formatting, no logic changes)
- `refactor`: Code refactoring
- `test`: Adding or updating tests
- `chore`: Maintenance tasks
- `perf`: Performance improvements
- `ci`: CI/CD changes
**Examples**:
```bash
# Good - conventional commits
feat(auth): add token refresh when less than 5 minutes remaining
fix(checklist): handle missing frontmatter gracefully
docs(readme): add quickstart guide for VSCode configuration
refactor(auth): simplify token validation logic
test(checklist): add unit tests for frontmatter parser
chore(deps): update mcp library to 1.23.3
# With body and footer
feat(mcp): implement get_checklist tool
Implement the get_checklist MCP tool that retrieves checklist content
by name. Includes YAML frontmatter parsing for metadata extraction.
Closes #123
# Bad - unclear commits
update code
fix bug
changes
WIP
```
### 8.3 Pull Request Format
**Convention**: Clear title and structured description
```markdown
## Summary
Brief description of what this PR does.
## Changes
- Bullet point list of changes
- Another change
## Test Plan
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed
## Checklist
- [ ] Code follows project standards
- [ ] Tests added/updated
- [ ] Documentation updated
- [ ] No security vulnerabilities
```
---
## 9. Documentation Standards
### 9.1 Code Comments
**Convention**: Explain "why", not "what"
```python
# Good - explains why
def should_refresh_token(token: dict, threshold_minutes: int = 5) -> bool:
"""Check if token should be refreshed.
We refresh proactively when less than 5 minutes remain to ensure
uninterrupted operation during long-running tool calls.
"""
expiry = token.get("expires_at", 0)
# Threshold is configurable but defaults to 5 minutes per spec clarification
return (expiry - time.time()) < (threshold_minutes * 60)
# Bad - states the obvious
def calculate_expiry(seconds: int) -> datetime:
# Get the current time
now = datetime.now()
# Add seconds to current time
expiry = now + timedelta(seconds=seconds)
# Return the expiry time
return expiry
```
### 9.2 Docstrings
**Convention**: Google style docstrings
```python
def get_checklist(name: str) -> ChecklistResponse:
"""Retrieve a checklist by name.
Loads the checklist file from the configured directory, parses
YAML frontmatter for metadata, and returns the complete content.
Args:
name: The checklist name (filename without .md extension).
Must match an existing file in CHECKLIST_DIR.
Returns:
ChecklistResponse containing name, description, and content.
Raises:
ChecklistNotFoundError: If no checklist with the given name exists.
FileReadError: If the checklist file cannot be read.
Example:
>>> service = ChecklistService("/path/to/checklists")
>>> result = service.get("coding")
>>> print(result["name"])
'coding'
"""
pass
```
### 9.3 Module Docstrings
```python
"""Authentication manager for SSO MCP Server.
This module provides the AuthManager class which orchestrates the OAuth 2.0
authentication flow with Azure Entra ID. It handles:
- Initial browser-based authentication with PKCE
- Secure token persistence using msal-extensions
- Automatic token refresh when expiry is near
- Session state management
Example:
>>> manager = AuthManager(client_id="...", tenant_id="...")
>>> await manager.ensure_authenticated()
>>> token = manager.get_access_token()
See Also:
- spec.md FR-001 through FR-005 for authentication requirements
- architecture.md §5.2 for component design
"""
```
### 9.4 Type Hints
**Convention**: Full type hints for all public APIs
```python
from typing import Optional, Any
from pathlib import Path
from datetime import datetime
# Good - complete type hints
def parse_frontmatter(content: str) -> tuple[dict[str, Any], str]:
"""Parse YAML frontmatter from markdown content."""
pass
def discover_checklists(directory: Path) -> list[Path]:
"""Discover all markdown files in directory."""
pass
async def get_access_token(manager: AuthManager) -> str | None:
"""Get current access token if authenticated."""
pass
# Class with type hints
class TokenStore:
def __init__(self, cache_path: Path) -> None:
self._cache_path = cache_path
def save(self, token_data: dict[str, Any]) -> None:
pass
def load(self) -> dict[str, Any] | None:
pass
def clear(self) -> None:
pass
```
---
## 10. Code Style Guide
### 10.1 Indentation
**Convention**: 4 spaces (Python standard)
```python
# Good - 4 spaces
def example_function():
if condition:
do_something()
if nested_condition:
do_nested_thing()
# Bad - inconsistent
def example_function():
if condition: # 2 spaces
do_something() # 8 spaces
```
### 10.2 Line Length
**Convention**: Maximum 88 characters (ruff default)
```python
# Good - break long lines
checklist_response = ChecklistResponse(
name=checklist_name,
description=metadata.get("description"),
content=content,
)
error_message = (
f"Checklist '{name}' not found. "
f"Available checklists: {', '.join(available_names)}"
)
# Bad - exceeds line length
checklist_response = ChecklistResponse(name=checklist_name, description=metadata.get("description"), content=content)
```
### 10.3 Blank Lines
**Convention**: 2 blank lines between top-level definitions, 1 within classes
```python
"""Module docstring."""
import os
from pathlib import Path
from mcp import McpError
CONSTANT_VALUE = 42
class FirstClass:
"""First class docstring."""
def __init__(self):
pass
def method_one(self):
pass
def method_two(self):
pass
class SecondClass:
"""Second class docstring."""
pass
def top_level_function():
"""Function docstring."""
pass
```
### 10.4 Import Ordering
**Convention**: isort-compatible ordering (handled by ruff)
```python
# Standard library imports
import os
import sys
from datetime import datetime
from pathlib import Path
from typing import Any
# Third-party imports
import msal
import structlog
from mcp.server.fastmcp import FastMCP
# Local imports
from sso_mcp_server.auth import AuthManager
from sso_mcp_server.checklists import ChecklistService
from sso_mcp_server.config import settings
```
### 10.5 String Formatting
**Convention**: f-strings for string interpolation
```python
# Good - f-strings
name = "coding"
message = f"Checklist '{name}' not found"
log.info(f"Processing checklist: {name}")
# Also acceptable - for complex formatting
template = "Checklist '{name}' has {count} items"
message = template.format(name=name, count=len(items))
# Bad - old-style formatting
message = "Checklist '%s' not found" % name
message = "Checklist '{}' not found".format(name)
```
---
## 11. Enforcement
### 11.1 Automated Tools
**Linting and Formatting**: ruff
```toml
# pyproject.toml
[tool.ruff]
target-version = "py311"
line-length = 88
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
"ARG", # flake8-unused-arguments
"SIM", # flake8-simplify
]
ignore = [
"E501", # line too long (handled by formatter)
]
[tool.ruff.isort]
known-first-party = ["sso_mcp_server"]
```
**Security Scanning**: bandit
```toml
# pyproject.toml
[tool.bandit]
exclude_dirs = ["tests"]
skips = ["B101"] # assert statements in tests
```
### 11.2 Pre-commit Hooks
```yaml
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-added-large-files
- id: check-merge-conflict
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/PyCQA/bandit
rev: 1.7.8
hooks:
- id: bandit
args: [-c, pyproject.toml]
additional_dependencies: ["bandit[toml]"]
```
### 11.3 CI/CD Integration
```yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v1
- run: uv sync --dev
- run: uv run ruff check .
- run: uv run ruff format --check .
- run: uv run bandit -c pyproject.toml -r src/
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v1
- run: uv sync --dev
- run: uv run pytest --cov=src/sso_mcp_server --cov-fail-under=80
```
### 11.4 Code Review Checklist
- [ ] Follows naming conventions (Section 3)
- [ ] Follows file structure (Section 4)
- [ ] MCP tools follow standards (Section 5)
- [ ] Has appropriate tests (Section 7)
- [ ] Has meaningful commit messages (Section 8)
- [ ] Has proper documentation and type hints (Section 9)
- [ ] Passes linter checks (Section 11)
- [ ] No security vulnerabilities (bandit)
---
## 12. Appendices
### 12.1 Glossary
| Term | Definition |
|------|------------|
| PascalCase | Capitalized first letter of each word: `AuthManager` |
| camelCase | Lowercase first letter, capitalized subsequent words: `authManager` |
| snake_case | Lowercase with underscores: `auth_manager` |
| SCREAMING_SNAKE_CASE | Uppercase with underscores: `AUTH_MANAGER` |
| MCP | Model Context Protocol - Anthropic's protocol for AI assistant tools |
| PKCE | Proof Key for Code Exchange - OAuth 2.0 security extension |
| SSO | Single Sign-On |
| FastMCP | High-level Python framework for building MCP servers |
### 12.2 Quick Reference Checklist
**Python Naming**:
- [ ] Variables: snake_case
- [ ] Constants: SCREAMING_SNAKE_CASE
- [ ] Functions: snake_case, verb-based
- [ ] Classes: PascalCase, nouns
- [ ] Files: snake_case.py
- [ ] Packages: lowercase_with_underscores
**MCP Tools**:
- [ ] Tool names: snake_case
- [ ] Input/Output: TypedDict with type hints
- [ ] Errors: MCP error codes with descriptive messages
- [ ] Documentation: Google-style docstrings
**Testing**:
- [ ] Files: test_<module>.py
- [ ] Functions: test_<what>_<condition>_<expected>
- [ ] Pattern: Arrange-Act-Assert
- [ ] Coverage: >80% for critical paths
**Git**:
- [ ] Branches: type/issue-description
- [ ] Commits: Conventional Commits format
- [ ] PRs: Clear title, structured description
### 12.3 Tool Configuration Summary
| Tool | Purpose | Config Location |
|------|---------|-----------------|
| ruff | Linting & formatting | pyproject.toml |
| bandit | Security scanning | pyproject.toml |
| pytest | Testing | pyproject.toml |
| pre-commit | Git hooks | .pre-commit-config.yaml |
| editorconfig | Editor settings | .editorconfig |
### 12.4 Resources
- [PEP 8 - Style Guide for Python Code](https://peps.python.org/pep-0008/)
- [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html)
- [Conventional Commits](https://www.conventionalcommits.org/)
- [MCP Specification](https://modelcontextprotocol.io/)
- [Ruff Documentation](https://docs.astral.sh/ruff/)
- [pytest Documentation](https://docs.pytest.org/)
---
**END OF STANDARDS DOCUMENT**
---
## Maintenance Notes
This document should be:
- **Reviewed quarterly** by the team
- **Updated** when new technologies are adopted
- **Referenced** in code reviews
- **Shared** with new team members during onboarding
- **Enforced** through automated tools
For questions or suggestions, contact the Development Team.