Data Visualization MCP Server

by markomitranic
Verified
import logging from mcp.server.models import InitializationOptions import mcp.types as types from mcp.server import NotificationOptions, Server import mcp.server.stdio from pydantic import AnyUrl from typing import Any import vl_convert as vlc import base64 logging.basicConfig( level=logging.INFO, # Set the log level format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[ logging.FileHandler("logs/mcp_vegalite_server.log"), # Log file path logging.StreamHandler(), # Optional: still output to the console ], ) logger = logging.getLogger("mcp_vegalite_server") logger.info("Starting MCP Vega-Lite Server") saved_data = { "sample_data": [ {"name": "Alice", "age": 25, "city": "New York"}, {"name": "Bob", "age": 30, "city": "San Francisco"}, {"name": "Charlie", "age": 35, "city": "Los Angeles"}, ] } SAVE_DATA_TOOL_DESCRIPTION = """ A tool which allows you to save data to a named table for later use in visualizations. When to use this tool: - Use this tool when you have data that you want to visualize later. How to use this tool: - Provide the name of the table to save the data to (for later reference) and the data itself. """.strip() VISUALIZE_DATA_TOOL_DESCRIPTION = """ A tool which allows you to produce a data visualization using the Vega-Lite grammar. When to use this tool: - At times, it will be advantageous to provide the user with a visual representation of some data, rather than just a textual representation. - This tool is particularly useful when the data is complex or has many dimensions, making it difficult to understand in a tabular format. It is not useful for singular data points. How to use this tool: - Prior to visualization, data must be saved to a named table using the save_data tool. - After saving the data, use this tool to visualize the data by providing the name of the table with the saved data and a Vega-Lite specification. """.strip() async def main(output_type: str): logger.info("Starting Vega-Lite MCP Server") server = Server("vegalite-manager") # Register handlers logger.debug("Registering handlers") @server.list_resources() async def handle_list_resources() -> list[types.Resource]: logger.debug("Handling list_resources request") return [] @server.read_resource() async def handle_read_resource(uri: AnyUrl) -> str: logger.debug(f"Handling read_resource request for URI: {uri}") path = str(uri).replace("memo://", "") raise ValueError(f"Unknown resource path: {path}") @server.list_prompts() async def handle_list_prompts() -> list[types.Prompt]: logger.debug("Handling list_prompts request") return [] @server.get_prompt() async def handle_get_prompt(name: str, arguments: dict[str, str] | None) -> types.GetPromptResult: logger.debug(f"Handling get_prompt request for {name} with args {arguments}") raise ValueError(f"Unknown prompt: {name}") @server.list_tools() async def handle_list_tools() -> list[types.Tool]: """List available tools""" return [ types.Tool( name="save_data", description=SAVE_DATA_TOOL_DESCRIPTION, inputSchema={ "type": "object", "properties": { "name": {"type": "string", "description": "The name of the table to save the data to"}, "data": { "type": "array", "items": {"type": "object", "description": "Row of the table as a dictionary/object"}, "description": "The data to save", }, }, "required": ["name", "data"], }, ), types.Tool( name="visualize_data", description=VISUALIZE_DATA_TOOL_DESCRIPTION, inputSchema={ "type": "object", "properties": { "data_name": { "type": "string", "description": "The name of the data table to visualize", }, "vegalite_specification": { "type": "string", "description": "The vegalite v5 specification for the visualization. Do not include the data field, as this will be added automatically.", }, }, "required": ["data_name", "vegalite_specification"], }, ), ] @server.call_tool() async def handle_call_tool( name: str, arguments: dict[str, Any] | None ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: """Handle tool execution requests""" logger.info(f"Handling tool execution request for {name} with args {arguments}") try: if name == "save_data": save_name = arguments["name"] saved_data[save_name] = arguments["data"] return [types.TextContent(type="text", text=f"Data saved successfully to table {save_name}")] elif name == "visualize_data": data_name = arguments["data_name"] vegalite_specification = eval(arguments["vegalite_specification"]) data = saved_data[data_name] vegalite_specification["data"] = {"values": data} if output_type == "png": png = vlc.vegalite_to_png(vl_spec=vegalite_specification, scale=2) png = base64.b64encode(png).decode("utf-8") return [types.ImageContent(type="image", data=png, mimeType="image/png")] else: return [ types.TextContent( type="text", text=f"Visualized data from table {data_name} with provided spec.", artifact=vegalite_specification, ) ] else: raise ValueError(f"Unknown tool: {name}") except Exception as e: return [types.TextContent(type="text", text=f"Error: {str(e)}")] async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): logger.info("Server running with stdio transport") await server.run( read_stream, write_stream, InitializationOptions( server_name="vegalite", server_version="0.1.0", capabilities=server.get_capabilities( notification_options=NotificationOptions(), experimental_capabilities={}, ), ), )