Glif

Official
  • src
import { z } from "zod"; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { formatOutput, getGlifDetails, runGlif, searchGlifs, getMyGlifs, createGlif, getMyUserInfo, getMyRecentRuns, } from "./api.js"; import { SearchParamsSchema } from "./types.js"; import { getSavedGlifs, saveGlif, removeGlif, SavedGlif, } from "./saved-glifs.js"; const RunGlifArgsSchema = z.object({ id: z.string(), inputs: z.array(z.string()), }); const ShowGlifArgsSchema = z.object({ id: z.string(), }); const CreateGlifArgsSchema = z.object({ name: z.string(), description: z.string(), }); const SaveGlifArgsSchema = z.object({ id: z.string(), toolName: z.string(), name: z.string().optional(), description: z.string().optional(), }); const RemoveGlifToolArgsSchema = z.object({ toolName: z.string(), }); export function setupToolHandlers(server: Server) { // Register tool definitions including saved glifs server.setRequestHandler(ListToolsRequestSchema, async () => { // Get saved glifs const savedGlifs = await getSavedGlifs(); // Create tool definitions for saved glifs const savedGlifTools = savedGlifs.map((glif) => ({ name: glif.toolName, description: `${glif.name}: ${glif.description}`, inputSchema: { type: "object", properties: { inputs: { type: "array", items: { type: "string", }, description: "Array of input values for the glif", }, }, required: ["inputs"], }, })); // Return all tool definitions return { tools: [...toolDefinitions, ...savedGlifTools], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { // Check if this is a saved glif tool const savedGlifs = await getSavedGlifs(); // Ensure savedGlifs is an array before using find const savedGlif = Array.isArray(savedGlifs) ? savedGlifs.find((g) => g.toolName === request.params.name) : undefined; if (savedGlif) { // Handle saved glif tool call const args = z .object({ inputs: z.array(z.string()) }) .parse(request.params.arguments); try { const result = await runGlif(savedGlif.id, args.inputs); // Ensure we have valid output if (!result || !result.output) { return { content: [ { type: "text", text: "No output received from glif run", }, ], }; } // Format the output const formattedOutput = formatOutput( result.outputFull?.type || "TEXT", result.output ); return { content: [ { type: "text", text: formattedOutput, }, ], }; } catch (error) { console.error("Error running glif:", error); return { content: [ { type: "text", text: `Error running glif: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } switch (request.params.name) { case "run_glif": { const args = RunGlifArgsSchema.parse(request.params.arguments); const result = await runGlif(args.id, args.inputs); return { content: [ { type: "text", text: formatOutput(result.outputFull.type, result.output), }, ], }; } case "list_featured_glifs": { const glifs = await searchGlifs({ featured: true }); const formattedGlifs = glifs .map( (glif) => `${glif.name} (${glif.id})\n${glif.description}\nBy: ${glif.user.name}\nRuns: ${glif.completedSpellRunCount}\n` ) .join("\n"); return { content: [ { type: "text", text: `Featured glifs:\n\n${formattedGlifs}`, }, ], }; } case "search_glifs": { const args = z .object({ query: z.string() }) .parse(request.params.arguments); const glifs = await searchGlifs({ q: args.query }); const formattedGlifs = glifs .map( (glif) => `${glif.name} (${glif.id})\n${glif.description}\nBy: ${glif.user.name}\nRuns: ${glif.completedSpellRunCount}\n` ) .join("\n"); return { content: [ { type: "text", text: `Search results for "${args.query}":\n\n${formattedGlifs}`, }, ], }; } case "glif_info": { const args = ShowGlifArgsSchema.parse(request.params.arguments); const { glif, recentRuns } = await getGlifDetails(args.id); // Extract input field names from glif data const inputFields = glif.data?.nodes ?.filter((node) => node.type.includes("input")) .map((node) => ({ name: node.name, type: node.type, params: node.params, })) ?? []; const details = [ `Name: ${glif.name}`, `Description: ${glif.description}`, `Created by: ${glif.user.name} (@${glif.user.username})`, `Created: ${new Date(glif.createdAt).toLocaleString()}`, `Runs: ${glif.completedSpellRunCount}`, `Average Duration: ${glif.averageDuration}ms`, `Likes: ${glif.likeCount}`, "", "Input Fields:", ...inputFields.map((field) => `- ${field.name} (${field.type})`), "", "Recent Runs:", ...recentRuns.map( (run) => `Time: ${new Date(run.createdAt).toLocaleString()} Duration: ${run.totalDuration}ms Output: ${ run.output ? formatOutput(run.outputType ?? "TEXT", run.output) : "No output" } By: ${run.user.name} (@${run.user.username}) ${ run.inputs ? Object.entries(run.inputs) .map(([key, value]) => ` Input "${key}": ${value}`) .join("\n") : "No inputs" }` ), ]; return { content: [ { type: "text", text: details.join("\n"), }, ], }; } case "my_glifs": { const glifs = await getMyGlifs(); const formattedGlifs = glifs .map( (glif) => `${glif.name} (${glif.id})\n${ glif.description }\nCreated: ${new Date(glif.createdAt).toLocaleString()}\nRuns: ${ glif.completedSpellRunCount }\n` ) .join("\n"); return { content: [ { type: "text", text: `Your glifs:\n\n${formattedGlifs}`, }, ], }; } case "debug_me": { const [user, glifs, recentRuns] = await Promise.all([ getMyUserInfo(), getMyGlifs(), getMyRecentRuns(), ]); const details = [ "User Information:", `ID: ${user.id}`, `Name: ${user.name}`, `Username: ${user.username}`, `Image: ${user.image}`, user.bio ? `Bio: ${user.bio}` : null, user.website ? `Website: ${user.website}` : null, user.location ? `Location: ${user.location}` : null, `Staff: ${user.staff ? "Yes" : "No"}`, `Subscriber: ${user.isSubscriber ? "Yes" : "No"}`, "", "Your Recent Glifs:", ...glifs .slice(0, 5) .map( (glif) => `- ${glif.name} (${glif.id})\n Created: ${new Date( glif.createdAt ).toLocaleString()}\n Runs: ${glif.completedSpellRunCount}` ), "", "Your Recent Runs:", ...recentRuns .slice(0, 5) .map( (run) => `- ${run.spell.name}\n Time: ${new Date( run.createdAt ).toLocaleString()}\n Duration: ${ run.totalDuration }ms\n Output: ${ run.output ? formatOutput(run.outputType ?? "TEXT", run.output) : "No output" }` ), ].filter(Boolean); return { content: [ { type: "text", text: details.join("\n"), }, ], }; } case "create_glif": { const args = CreateGlifArgsSchema.parse(request.params.arguments); try { await createGlif(args.name, args.description); return { content: [ { type: "text", text: "Coming soon!", }, ], }; } catch (error) { return { content: [ { type: "text", text: "Create glif functionality coming soon!", }, ], }; } } case "save_glif_as_tool": { const args = SaveGlifArgsSchema.parse(request.params.arguments); // Get glif details to use name/description if not provided const { glif } = await getGlifDetails(args.id); const savedGlif: SavedGlif = { id: args.id, toolName: args.toolName, name: args.name || glif.name, description: args.description || glif.description || "", createdAt: new Date().toISOString(), }; await saveGlif(savedGlif); return { content: [ { type: "text", text: `Successfully saved glif "${savedGlif.name}" as tool "${savedGlif.toolName}"`, }, ], }; } case "remove_glif_tool": { const args = RemoveGlifToolArgsSchema.parse(request.params.arguments); const removed = await removeGlif(args.toolName); return { content: [ { type: "text", text: removed ? `Successfully removed tool "${args.toolName}"` : `Tool "${args.toolName}" not found`, }, ], }; } case "list_saved_glif_tools": { const savedGlifs = await getSavedGlifs(); // Ensure savedGlifs is a valid array with content if (!Array.isArray(savedGlifs) || savedGlifs.length === 0) { return { content: [ { type: "text", text: "No saved glif tools found.", }, ], }; } try { const formattedGlifs = savedGlifs .map((glif) => { // Ensure we have valid date string for createdAt let dateStr = "Unknown date"; try { dateStr = new Date(glif.createdAt).toLocaleString(); } catch (err) { // Use fallback if date is invalid } return `${glif.name} (tool: ${glif.toolName})\n${glif.description}\nOriginal Glif ID: ${glif.id}\nSaved: ${dateStr}\n`; }) .join("\n"); return { content: [ { type: "text", text: `Saved glif tools:\n\n${formattedGlifs}`, }, ], }; } catch (error) { console.error("Error formatting saved glifs:", error); // Return a fallback response with the raw data return { content: [ { type: "text", text: `Saved glif tools:\n\n${JSON.stringify( savedGlifs, null, 2 )}`, }, ], }; } } default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } }); } export const toolDefinitions = [ { name: "save_glif_as_tool", description: "Save a glif as a custom tool", inputSchema: { type: "object", properties: { id: { type: "string", description: "The ID of the glif to save", }, toolName: { type: "string", description: "The name to use for the tool (must be unique)", }, name: { type: "string", description: "Optional custom name for the tool (defaults to glif name)", }, description: { type: "string", description: "Optional custom description (defaults to glif description)", }, }, required: ["id", "toolName"], }, }, { name: "remove_glif_tool", description: "Remove a saved glif tool", inputSchema: { type: "object", properties: { toolName: { type: "string", description: "The tool name of the saved glif to remove", }, }, required: ["toolName"], }, }, { name: "reset_saved_glifs", description: "Reset all saved glifs (does not affect other tools)", inputSchema: { type: "object", properties: {}, required: [], }, }, { name: "run_glif", description: "Run a glif with the specified ID and inputs", inputSchema: { type: "object", properties: { id: { type: "string", description: "The ID of the glif to run", }, inputs: { type: "array", items: { type: "string", }, description: "Array of input values for the glif", }, }, required: ["id", "inputs"], }, }, { name: "list_featured_glifs", description: "Get a curated list of featured glifs", inputSchema: { type: "object", properties: {}, required: [], }, }, // { // name: "search_glifs", // description: "Search for glifs by query string", // inputSchema: { // type: "object", // properties: { // query: { // type: "string", // description: "Search query string", // }, // }, // required: ["query"], // }, // }, { name: "glif_info", description: "Get detailed information about a glif including input fields", inputSchema: { type: "object", properties: { id: { type: "string", description: "The ID of the glif to show details for", }, }, required: ["id"], }, }, { name: "my_glifs", description: "Get a list of your glifs", inputSchema: { type: "object", properties: {}, required: [], }, }, { name: "my_glif_user_info", description: "Get detailed information about your user account, recent glifs, and recent runs", inputSchema: { type: "object", properties: {}, required: [], }, }, // TODO: Endpoint not yet available // { // name: "my_liked_glifs", // description: "Get a list of glifs liked by a specific user", // inputSchema: { // type: "object", // properties: { // userId: { // type: "string", // description: "The ID of the user whose liked glifs to fetch", // }, // }, // required: ["userId"], // }, // }, // { // name: "create_glif", // description: "Create a new glif (Coming soon!)", // inputSchema: { // type: "object", // properties: { // name: { // type: "string", // description: "Name of the glif to create", // }, // description: { // type: "string", // description: "Description of the glif", // }, // }, // required: ["name", "description"], // }, // }, ];