Skip to main content
Glama
schema.py8.17 kB
"""Data models for the Zettelkasten MCP server.""" import sys import time import datetime from datetime import datetime as dt import random import inspect from enum import Enum from typing import Any, Dict, List, Optional, Set, Union from pydantic import BaseModel, Field, field_validator import threading # Thread-safe counter for uniqueness _id_lock = threading.Lock() _last_timestamp = 0 _counter = 0 def generate_id() -> str: """Generate an ISO 8601 compliant timestamp-based ID with guaranteed uniqueness (pseudo-nanosecond precision). Returns: A string in format "YYYYMMDDTHHMMSSssssssccc" where: - YYYYMMDD is the date - T is the ISO 8601 date/time separator - HHMMSS is the time (hours, minutes, seconds) - ssssss is the 6-digit microsecond component (from time.time()) - ccc is a 3-digit counter for uniqueness within the same microsecond The format follows ISO 8601 basic format with extended precision, allowing up to 1 billion unique IDs per second. """ global _last_timestamp, _counter with _id_lock: # Get current timestamp with microsecond precision now = datetime.datetime.now() # Create a timestamp in microseconds current_timestamp = int(now.timestamp() * 1_000_000) # If multiple IDs generated in same microsecond, increment counter if current_timestamp == _last_timestamp: _counter += 1 else: _last_timestamp = current_timestamp _counter = 0 # Ensure counter doesn't overflow our 3 digits _counter %= 1000 # Format as ISO 8601 basic format with microseconds and counter date_time = now.strftime('%Y%m%dT%H%M%S') microseconds = now.microsecond return f"{date_time}{microseconds:06d}{_counter:03d}" class LinkType(str, Enum): """Types of links between notes.""" REFERENCE = "reference" # Simple reference to another note EXTENDS = "extends" # Current note extends another note EXTENDED_BY = "extended_by" # Current note is extended by another note REFINES = "refines" # Current note refines another note REFINED_BY = "refined_by" # Current note is refined by another note CONTRADICTS = "contradicts" # Current note contradicts another note CONTRADICTED_BY = "contradicted_by" # Current note is contradicted by another note QUESTIONS = "questions" # Current note questions another note QUESTIONED_BY = "questioned_by" # Current note is questioned by another note SUPPORTS = "supports" # Current note supports another note SUPPORTED_BY = "supported_by" # Current note is supported by another note RELATED = "related" # Notes are related in some way class Link(BaseModel): """A link between two notes.""" source_id: str = Field(..., description="ID of the source note") target_id: str = Field(..., description="ID of the target note") link_type: LinkType = Field(default=LinkType.REFERENCE, description="Type of link") description: Optional[str] = Field(default=None, description="Optional description of the link") created_at: datetime.datetime = Field( default_factory=datetime.datetime.now, description="When the link was created" ) model_config = { "validate_assignment": True, "extra": "forbid", "frozen": True # Links are immutable } class NoteType(str, Enum): """Types of notes in a Zettelkasten.""" FLEETING = "fleeting" # Quick, temporary notes LITERATURE = "literature" # Notes from reading material PERMANENT = "permanent" # Permanent, well-formulated notes STRUCTURE = "structure" # Structure/index notes that organize other notes HUB = "hub" # Hub notes that serve as entry points class Tag(BaseModel): """A tag for categorizing notes.""" name: str = Field(..., description="Tag name") model_config = { "validate_assignment": True, "frozen": True } def __str__(self) -> str: """Return string representation of tag.""" return self.name class Note(BaseModel): """A Zettelkasten note.""" id: str = Field(default_factory=generate_id, description="Unique ID of the note") title: str = Field(..., description="Title of the note") content: str = Field(..., description="Content of the note") note_type: NoteType = Field(default=NoteType.PERMANENT, description="Type of note") tags: List[Tag] = Field(default_factory=list, description="Tags for categorization") links: List[Link] = Field(default_factory=list, description="Links to other notes") created_at: datetime.datetime = Field( default_factory=datetime.datetime.now, description="When the note was created" ) updated_at: datetime.datetime = Field( default_factory=datetime.datetime.now, description="When the note was last updated" ) metadata: Dict[str, Any] = Field( default_factory=dict, description="Additional metadata for the note" ) model_config = { "validate_assignment": True, "extra": "forbid" } @field_validator("title") @classmethod def validate_title(cls, v: str) -> str: """Validate that the title is not empty.""" if not v.strip(): raise ValueError("Title cannot be empty") return v def add_tag(self, tag: Union[str, Tag]) -> None: """Add a tag to the note.""" if isinstance(tag, str): tag = Tag(name=tag) # Check if tag already exists tag_names = {t.name for t in self.tags} if tag.name not in tag_names: self.tags.append(tag) self.updated_at = datetime.datetime.now() def remove_tag(self, tag: Union[str, Tag]) -> None: """Remove a tag from the note.""" tag_name = tag.name if isinstance(tag, Tag) else tag self.tags = [t for t in self.tags if t.name != tag_name] self.updated_at = datetime.datetime.now() def add_link(self, target_id: str, link_type: LinkType = LinkType.REFERENCE, description: Optional[str] = None) -> None: """Add a link to another note.""" # Check if link already exists for link in self.links: if link.target_id == target_id and link.link_type == link_type: return # Link already exists link = Link( source_id=self.id, target_id=target_id, link_type=link_type, description=description ) self.links.append(link) self.updated_at = datetime.datetime.now() def remove_link(self, target_id: str, link_type: Optional[LinkType] = None) -> None: """Remove a link to another note.""" if link_type: self.links = [ link for link in self.links if not (link.target_id == target_id and link.link_type == link_type) ] else: self.links = [link for link in self.links if link.target_id != target_id] self.updated_at = datetime.datetime.now() def get_linked_note_ids(self) -> Set[str]: """Get all note IDs that this note links to.""" return {link.target_id for link in self.links} def to_markdown(self) -> str: """Convert the note to a markdown formatted string.""" from zettelkasten_mcp.config import config # Format tags tags_str = ", ".join([tag.name for tag in self.tags]) # Format links links_str = "" if self.links: links_str = "\n".join([ f"- [{link.link_type}] [[{link.target_id}]] {link.description or ''}" for link in self.links ]) # Apply template return config.default_note_template.format( title=self.title, content=self.content, created_at=self.created_at.isoformat(), tags=tags_str, links=links_str )

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Liam-Deacon/zettelkasten-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server