models.py•4.42 kB
"""Pydantic models for MCP CopyQ."""
from enum import Enum
from typing import Literal
from pydantic import BaseModel, Field
# === Enums ===
class ReadMode(str, Enum):
TREE = "tree"
LIST = "list"
ITEM = "item"
SEARCH = "search"
class WriteMode(str, Enum):
ADD = "add"
UPDATE = "update"
DELETE = "delete"
MOVE = "move"
TAB_CREATE = "tab_create"
TAB_DELETE = "tab_delete"
class EditMode(str, Enum):
REPLACE = "replace"
APPEND = "append"
PREPEND = "prepend"
SUBSTITUTE = "substitute"
REMOVE = "remove" # for tags only
class FieldType(str, Enum):
TEXT = "text"
NOTE = "note"
TAGS = "tags"
class SearchIn(str, Enum):
TEXT = "text"
NOTE = "note"
TAGS = "tags"
ALL = "all"
class Scope(str, Enum):
MCP = "mcp" # only mcp/* tabs (default, safe)
ALL = "all" # all CopyQ tabs (read-only for external)
EXTERNAL = "external" # only non-mcp tabs (read-only)
class Intent(str, Enum):
EXECUTE = "execute"
PREVIEW = "preview"
# === Request Models ===
class ReadRequest(BaseModel):
"""Parameters for mcp_read tool."""
mode: ReadMode = Field(..., description="Operation mode: tree, list, item, search")
tab: str = Field(default="", description="Tab path: 'info', 'workspace/proj1' or full path like '&clipboard'")
index: int | None = Field(default=None, description="Item index (for mode=item)")
query: str | None = Field(default=None, description="Search query regex (for mode=search)")
search_in: SearchIn = Field(default=SearchIn.ALL, description="Where to search")
scope: Scope = Field(default=Scope.MCP, description="Tab scope: mcp (default), all, external")
max_depth: int = Field(default=2, ge=1, le=10, description="Max depth for tree")
max_items: int = Field(default=20, ge=1, le=100, description="Max items to return")
skip: int = Field(default=0, ge=0, description="Skip N items (pagination)")
include_text: bool = Field(default=True, description="Include text/preview")
include_tags: bool = Field(default=True, description="Include tags")
include_note: bool = Field(default=False, description="Include note")
class WriteRequest(BaseModel):
"""Parameters for mcp_write tool."""
mode: WriteMode = Field(..., description="Operation mode")
tab: str | None = Field(default=None, description="Tab path")
index: int | None = Field(default=None, description="Item index")
text: str | None = Field(default=None, description="Text content")
tags: list[str] | None = Field(default=None, description="Tags list")
note: str | None = Field(default=None, description="Note content")
field: FieldType | None = Field(default=None, description="Field to update")
edit_mode: EditMode = Field(default=EditMode.REPLACE, description="How to edit")
match: str | None = Field(default=None, description="String to match (for substitute)")
to_tab: str | None = Field(default=None, description="Destination tab (for move)")
path: str | None = Field(default=None, description="Tab path (for tab_create/tab_delete)")
intent: Intent = Field(default=Intent.EXECUTE, description="Execute or preview")
class ValidateRequest(BaseModel):
"""Parameters for mcp_validate tool."""
tool: Literal["read", "write"] = Field(..., description="Tool to validate")
params: dict = Field(..., description="Parameters to validate")
# === Response Models ===
class TabInfo(BaseModel):
"""Tab metadata."""
path: str
count: int
children: list["TabInfo"] = Field(default_factory=list)
preview_items: list["ItemPreview"] = Field(default_factory=list)
class ItemPreview(BaseModel):
"""Short item preview for list/tree."""
index: int
text_preview: str = ""
tags: list[str] = Field(default_factory=list)
has_note: bool = False
class ItemFull(BaseModel):
"""Full item data."""
index: int
text: str = ""
tags: list[str] = Field(default_factory=list)
note: str = ""
class SearchResult(BaseModel):
"""Search result item."""
tab: str
index: int
text_preview: str = ""
tags: list[str] = Field(default_factory=list)
match_in: str # where match was found: text, note, tags
# === Constants ===
MCP_ROOT = "mcp"
ALLOWED_ROOT_TABS = ["info", "заметки", "workspace"]
TABS_WITH_SUBTABS = ["workspace"]
PREVIEW_LENGTH = 80