We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/CortaLabs/scribe_mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Bridge manifest schema and configuration models."""
from dataclasses import dataclass, field, asdict
from enum import Enum
from typing import Dict, List, Optional, Any
import os
import json
class BridgeState(Enum):
"""Bridge lifecycle states."""
REGISTERED = "registered"
ACTIVE = "active"
INACTIVE = "inactive"
ERROR = "error"
UNREGISTERED = "unregistered"
@dataclass
class LogTypeConfig:
"""Configuration for a custom log type."""
log_type: str
path_template: str
format: str = "markdown" # markdown|jsonl
metadata_requirements: List[str] = field(default_factory=list)
rotation_threshold: Optional[int] = None
description: str = ""
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return {
"log_type": self.log_type,
"path_template": self.path_template,
"format": self.format,
"metadata_requirements": self.metadata_requirements,
"rotation_threshold": self.rotation_threshold,
"description": self.description,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "LogTypeConfig":
"""Create from dictionary."""
return cls(
log_type=data["log_type"],
path_template=data["path_template"],
format=data.get("format", "markdown"),
metadata_requirements=data.get("metadata_requirements", []),
rotation_threshold=data.get("rotation_threshold"),
description=data.get("description", ""),
)
@dataclass
class HookConfig:
"""Configuration for a bridge hook."""
hook_name: str
callback_type: str = "async" # sync|async|webhook
timeout_ms: int = 5000
critical: bool = False
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return {
"hook_name": self.hook_name,
"callback_type": self.callback_type,
"timeout_ms": self.timeout_ms,
"critical": self.critical,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "HookConfig":
"""Create from dictionary."""
return cls(
hook_name=data["hook_name"],
callback_type=data.get("callback_type", "async"),
timeout_ms=data.get("timeout_ms", 5000),
critical=data.get("critical", False),
)
@dataclass
class BridgeProjectConfig:
"""Project management configuration for a bridge."""
can_create_projects: bool = False
project_prefix: Optional[str] = None
auto_tag: List[str] = field(default_factory=list)
default_metadata: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return {
"can_create_projects": self.can_create_projects,
"project_prefix": self.project_prefix,
"auto_tag": self.auto_tag,
"default_metadata": self.default_metadata,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "BridgeProjectConfig":
"""Create from dictionary."""
return cls(
can_create_projects=data.get("can_create_projects", False),
project_prefix=data.get("project_prefix"),
auto_tag=data.get("auto_tag", []),
default_metadata=data.get("default_metadata", {}),
)
@dataclass
class BridgeValidationConfig:
"""Validation configuration for a bridge."""
mode: str = "lenient" # strict|lenient|custom
schema: Optional[Dict[str, Any]] = None
custom_validator: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return {
"mode": self.mode,
"schema": self.schema,
"custom_validator": self.custom_validator,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "BridgeValidationConfig":
"""Create from dictionary."""
return cls(
mode=data.get("mode", "lenient"),
schema=data.get("schema"),
custom_validator=data.get("custom_validator"),
)
@dataclass
class BridgeManifest:
"""Complete bridge manifest configuration."""
bridge_id: str
name: str
version: str
description: str
author: str
log_config: Dict[str, LogTypeConfig] = field(default_factory=dict)
hooks: Dict[str, HookConfig] = field(default_factory=dict)
project_config: BridgeProjectConfig = field(default_factory=BridgeProjectConfig)
validation: BridgeValidationConfig = field(default_factory=BridgeValidationConfig)
api_key: Optional[str] = None
permissions: List[str] = field(default_factory=list)
min_scribe_version: str = "2.1.0"
max_scribe_version: Optional[str] = None
def validate(self) -> List[str]:
"""
Validate manifest configuration.
Returns:
List of error messages (empty if valid)
"""
errors = []
# Required fields
if not self.bridge_id:
errors.append("bridge_id is required")
if not self.name:
errors.append("name is required")
if not self.version:
errors.append("version is required")
# Validate bridge_id format (alphanumeric, hyphens, underscores only)
if self.bridge_id and not all(c.isalnum() or c in ("-", "_") for c in self.bridge_id):
errors.append("bridge_id must contain only alphanumeric characters, hyphens, and underscores")
# Validate log_config
for log_type, config in self.log_config.items():
if not config.log_type:
errors.append(f"log_type is required for log config entry '{log_type}'")
if not config.path_template:
errors.append(f"path_template is required for log config '{log_type}'")
if config.format not in ("markdown", "jsonl"):
errors.append(f"Invalid format '{config.format}' for log config '{log_type}' (must be 'markdown' or 'jsonl')")
# Validate hooks
for hook_name, config in self.hooks.items():
if not config.hook_name:
errors.append(f"hook_name is required for hook config entry '{hook_name}'")
if config.callback_type not in ("sync", "async", "webhook"):
errors.append(f"Invalid callback_type '{config.callback_type}' for hook '{hook_name}' (must be 'sync', 'async', or 'webhook')")
if config.timeout_ms <= 0:
errors.append(f"timeout_ms must be positive for hook '{hook_name}'")
# Validate validation config
if self.validation.mode not in ("strict", "lenient", "custom"):
errors.append(f"Invalid validation mode '{self.validation.mode}' (must be 'strict', 'lenient', or 'custom')")
if self.validation.mode == "custom" and not self.validation.custom_validator:
errors.append("custom_validator is required when validation mode is 'custom'")
return errors
def expand_env_vars(self) -> None:
"""
Expand environment variables in api_key.
Supports ${VAR_NAME} syntax. If variable is not found,
api_key will be set to None.
"""
if self.api_key and self.api_key.startswith("${") and self.api_key.endswith("}"):
var_name = self.api_key[2:-1]
self.api_key = os.environ.get(var_name)
def to_json(self) -> str:
"""
Serialize manifest to JSON string.
Returns:
JSON string representation
"""
data = {
"bridge_id": self.bridge_id,
"name": self.name,
"version": self.version,
"description": self.description,
"author": self.author,
"log_config": {k: v.to_dict() for k, v in self.log_config.items()},
"hooks": {k: v.to_dict() for k, v in self.hooks.items()},
"project_config": self.project_config.to_dict(),
"validation": self.validation.to_dict(),
"api_key": self.api_key,
"permissions": self.permissions,
"min_scribe_version": self.min_scribe_version,
"max_scribe_version": self.max_scribe_version,
}
return json.dumps(data, indent=2)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "BridgeManifest":
"""
Create manifest from dictionary (parsed YAML/JSON).
Args:
data: Dictionary containing manifest configuration
Returns:
BridgeManifest instance
"""
# Parse nested configs
log_config = {}
for log_type, config_data in data.get("log_config", {}).items():
log_config[log_type] = LogTypeConfig.from_dict(config_data)
hooks = {}
for hook_name, hook_data in data.get("hooks", {}).items():
hooks[hook_name] = HookConfig.from_dict(hook_data)
project_config = BridgeProjectConfig.from_dict(data.get("project_config", {}))
validation = BridgeValidationConfig.from_dict(data.get("validation", {}))
return cls(
bridge_id=data["bridge_id"],
name=data["name"],
version=data["version"],
description=data["description"],
author=data["author"],
log_config=log_config,
hooks=hooks,
project_config=project_config,
validation=validation,
api_key=data.get("api_key"),
permissions=data.get("permissions", []),
min_scribe_version=data.get("min_scribe_version", "2.1.0"),
max_scribe_version=data.get("max_scribe_version"),
)