Skip to main content
Glama

LinkedIn Content Creation MCP Server

by chrishayuk
artifact_preview.py8.17 kB
""" Artifact-based preview system using chuk-artifacts. Provides session-isolated, secure preview URLs with automatic cleanup. """ import uuid from typing import Any, List, Optional from chuk_artifacts import ArtifactStore from chuk_artifacts.config import configure_filesystem, configure_memory class ArtifactPreviewManager: """ Manages draft previews using chuk-artifacts for session-based storage. Features: - Session-isolated previews (each session only sees their own drafts) - Automatic cleanup of expired artifacts - Presigned URLs for secure access - Grid architecture: grid/{sandbox_id}/{session_id}/{artifact_id} """ def __init__(self, provider: str = "memory", sandbox_id: str = "linkedin-mcp"): """ Initialize artifact preview manager. Args: provider: Storage provider (memory, filesystem, s3, ibm-cos) sandbox_id: Sandbox identifier for grid isolation """ self.provider = provider self.sandbox_id = sandbox_id self._store: Optional[ArtifactStore] = None self._current_session: Optional[str] = None async def __aenter__(self) -> "ArtifactPreviewManager": """Async context manager entry.""" # Configure provider if self.provider == "memory": configure_memory() elif self.provider == "filesystem": configure_filesystem(root=f".artifacts/{self.sandbox_id}") elif self.provider in ("s3", "ibm-cos"): # S3 configuration should be done via environment variables # or explicit configuration before creating the manager pass self._store = ArtifactStore() await self._store.__aenter__() return self async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: """Async context manager exit.""" if self._store: await self._store.__aexit__(exc_type, exc_val, exc_tb) def create_session(self, user_id: Optional[str] = None) -> str: """ Create a new session for a user. Args: user_id: Optional user identifier (generates UUID if not provided) Returns: Session ID """ if not user_id: user_id = str(uuid.uuid4()) session_id = f"user_{user_id}" self._current_session = session_id return session_id def set_session(self, session_id: str) -> None: """ Set the current session. Args: session_id: Session ID to use """ self._current_session = session_id def get_session(self) -> Optional[str]: """ Get the current session ID. Returns: Current session ID or None """ return self._current_session async def store_preview( self, html_content: str, draft_id: str, draft_name: str, session_id: Optional[str] = None ) -> str: """ Store a draft preview as an artifact. Args: html_content: HTML preview content draft_id: Draft ID draft_name: Draft name for metadata session_id: Optional session ID (uses current if not provided) Returns: Artifact ID """ if not self._store: raise RuntimeError("ArtifactStore not initialized. Use 'async with' context.") # Use provided session or current session session = session_id or self._current_session if not session: raise ValueError("No session ID provided or set") # Store HTML as artifact artifact_id_result: str = await self._store.store( data=html_content.encode("utf-8"), mime="text/html", summary=f"LinkedIn Post Preview: {draft_name}", filename=f"{draft_id}_preview.html", user_id=session, meta={ "draft_id": draft_id, "draft_name": draft_name, "type": "preview", "sandbox_id": self.sandbox_id, }, ) return artifact_id_result async def get_preview( self, artifact_id: str, session_id: Optional[str] = None ) -> Optional[bytes]: """ Retrieve a preview artifact. Args: artifact_id: Artifact ID to retrieve session_id: Optional session ID (uses current if not provided) Returns: HTML content as bytes, or None if not found or access denied """ if not self._store: raise RuntimeError("ArtifactStore not initialized. Use 'async with' context.") try: # Retrieve enforces session isolation automatically content_result: bytes | None = await self._store.retrieve(artifact_id) return content_result except Exception: # Access denied or not found return None async def get_preview_url( self, artifact_id: str, session_id: Optional[str] = None, expires_in: int = 3600 ) -> Optional[str]: """ Get a presigned URL for a preview. Args: artifact_id: Artifact ID session_id: Optional session ID (uses current if not provided) expires_in: URL expiration in seconds (default: 1 hour) Returns: Presigned URL or None if not available """ if not self._store: raise RuntimeError("ArtifactStore not initialized. Use 'async with' context.") try: # Generate presigned URL (duration-based) url_result: str | None = await self._store.presign_short( artifact_id, expires_in=expires_in ) return url_result except Exception: # Not supported by provider or access denied return None async def list_previews(self, session_id: Optional[str] = None) -> List[Any]: """ List all previews for a session. Args: session_id: Optional session ID (uses current if not provided) Returns: List of artifact metadata dictionaries """ if not self._store: raise RuntimeError("ArtifactStore not initialized. Use 'async with' context.") session = session_id or self._current_session if not session: raise ValueError("No session ID provided or set") # List artifacts for session artifacts = await self._store.list_by_session(session) # Filter for preview artifacts (if metadata is available) previews = [ artifact for artifact in artifacts if artifact.get("meta", {}).get("type") == "preview" ] return previews async def delete_preview(self, artifact_id: str, session_id: Optional[str] = None) -> bool: """ Delete a preview artifact. Args: artifact_id: Artifact ID to delete session_id: Optional session ID (uses current if not provided) Returns: True if deleted, False if not found or access denied """ if not self._store: raise RuntimeError("ArtifactStore not initialized. Use 'async with' context.") try: await self._store.delete(artifact_id) return True except Exception: return False # Global artifact preview manager instance _artifact_manager: Optional[ArtifactPreviewManager] = None async def get_artifact_manager( provider: str = "memory", sandbox_id: str = "linkedin-mcp" ) -> ArtifactPreviewManager: """ Get or create the global artifact preview manager. Args: provider: Storage provider (memory, filesystem, s3, ibm-cos) sandbox_id: Sandbox identifier Returns: ArtifactPreviewManager instance """ global _artifact_manager if _artifact_manager is None: _artifact_manager = ArtifactPreviewManager(provider=provider, sandbox_id=sandbox_id) # Initialize the store await _artifact_manager.__aenter__() return _artifact_manager

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/chrishayuk/chuk-mcp-linkedin'

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