MCP Journaling Server
by mtct
- src
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { WebflowClient } from "webflow-api";
import { z } from "zod";
const accessToken =
process.env.WEBFLOW_API_TOKEN ||
(() => {
throw new Error("WEBFLOW_API_TOKEN is not defined");
})();
// Initialize the server
const server = new Server(
{
name: "webflow-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
const schemas = {
toolInputs: {
getSite: z.object({
siteId: z.string().min(1, "Site ID is required"),
}),
getSites: z.object({}),
},
};
interface WebflowApiError {
status?: number;
message: string;
code?: string;
}
type ToolHandler = (args: unknown) => Promise<{
content: Array<{ type: "text"; text: string }>;
}>;
// Utility functions
function isWebflowApiError(error: unknown): error is WebflowApiError {
return error !== null && typeof error === "object" && "code" in error;
}
function formatDate(date: Date | undefined | null): string {
if (!date) return "N/A";
return date.toLocaleString();
}
// Tool definitions
const TOOL_DEFINITIONS = [
{
name: "get_site",
description:
"Retrieve detailed information about a specific Webflow site by ID, including workspace, creation date, display name, and publishing details",
inputSchema: {
type: "object",
properties: {
siteId: {
type: "string",
description: "The unique identifier of the Webflow site",
},
},
required: ["siteId"],
},
},
{
name: "get_sites",
description:
"Retrieve a list of all Webflow sites accessible to the authenticated user",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
];
// Tool handlers
const toolHandlers: Record<string, ToolHandler> = {
get_site: async (args: unknown) => {
const { siteId } = schemas.toolInputs.getSite.parse(args);
try {
const webflow = new WebflowClient({ accessToken });
const site = await webflow.sites.get(siteId);
if (!site) {
throw new Error("Site not found");
}
const formattedSite = `• Site Details:
ID: ${site.id}
Display Name: ${site.displayName}
Short Name: ${site.shortName}
- Workspace Information:
Workspace ID: ${site.workspaceId}
- Dates:
Created On: ${formatDate(site?.createdOn)}
Last Published: ${formatDate(site?.lastPublished)}
- URLs:
Preview URL: ${site.previewUrl || "N/A"}`;
return {
content: [
{
type: "text" as const,
text: formattedSite,
},
],
};
} catch (error: unknown) {
if (isWebflowApiError(error) && error.code === "NOT_FOUND") {
return {
content: [
{
type: "text" as const,
text: `Site with ID ${siteId} not found.`,
},
],
};
}
console.error("Error fetching site:", error);
throw new Error("Failed to fetch site details");
}
},
get_sites: async () => {
try {
const webflow = new WebflowClient({ accessToken });
const { sites } = await webflow.sites.list();
if (!Array.isArray(sites) || sites.length === 0) {
return {
content: [
{
type: "text" as const,
text: "No sites found for this account.",
},
],
};
}
const formattedSites = sites
.map(
(site) => `
• Site: ${site.displayName}
- ID: ${site.id}
- Workspace: ${site.workspaceId}
- Created: ${formatDate(site?.createdOn)}
- Last Published: ${formatDate(site?.lastPublished)}
- Preview URL: ${site.previewUrl || "N/A"}
`
)
.join("\n");
return {
content: [
{
type: "text" as const,
text: `Found ${sites.length} sites:\n${formattedSites}`,
},
],
};
} catch (error: unknown) {
console.error("Error fetching sites:", error);
throw new Error("Failed to fetch sites list");
}
},
};
// Register tool handlers
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error("Tools requested by client");
return { tools: TOOL_DEFINITIONS };
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const handler = toolHandlers[name as keyof typeof toolHandlers];
if (!handler) {
throw new Error(`Unknown tool: ${name}`);
}
return await handler(args);
} catch (error) {
console.error(`Error executing tool ${name}:`, error);
throw error;
}
});
// Start the server
async function main() {
try {
// Check for required environment variables
const requiredEnvVars = ["WEBFLOW_API_TOKEN"];
const missingVars = requiredEnvVars.filter(
(varName) => !process.env[varName]
);
if (missingVars.length > 0) {
console.error(
`Missing required environment variables: ${missingVars.join(", ")}`
);
process.exit(1);
}
console.error("Starting server with env vars:", {
WEBFLOW_API_TOKEN: "[REDACTED]",
});
const transport = new StdioServerTransport();
console.error("Created transport");
await server.connect(transport);
console.error("Connected to transport");
console.error("Webflow MCP Server running on stdio");
} catch (error) {
console.error("Startup error:", error);
process.exit(1);
}
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});