/**
* fal.ai MCP Server - Main entry point
*
* A Model Context Protocol (MCP) server for interacting with fal.ai models and services.
* Provides tools to list models, generate content, and manage file uploads.
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import {
authenticatedRequest,
publicRequest,
sanitizeParameters,
uploadFile,
FalAPIError
} from "./utils.js";
import {
FAL_BASE_URL,
FAL_QUEUE_URL,
FAL_DIRECT_URL,
SERVER_NAME,
SERVER_VERSION
} from "./config.js";
/**
* Configuration schema for the fal.ai MCP server
* Users must provide their fal.ai API key to use authenticated endpoints
*/
export const configSchema = z.object({
apiKey: z.string().describe("Your fal.ai API key (get one at https://fal.ai)"),
});
type Config = z.infer<typeof configSchema>;
/**
* Create and configure the fal.ai MCP server
*/
export default function createServer({ config }: { config: Config }) {
const server = new McpServer({
name: SERVER_NAME,
version: SERVER_VERSION,
});
// ============================================================================
// MODEL TOOLS
// ============================================================================
/**
* List available fal.ai models with pagination
*/
server.registerTool(
"models",
{
title: "List Models",
description: "List available models on fal.ai. Use pagination to avoid listing all models at once.",
inputSchema: {
page: z.number().optional().describe("The page number of models to retrieve (pagination)"),
total: z.number().optional().describe("The total number of models to retrieve per page"),
},
},
async ({ page, total }) => {
try {
let url = `${FAL_BASE_URL}/models`;
const params: string[] = [];
if (page !== undefined) {
params.push(`page=${page}`);
}
if (total !== undefined) {
params.push(`total=${total}`);
}
if (params.length > 0) {
url += `?${params.join('&')}`;
}
const result = await publicRequest(url);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof FalAPIError
? error.message
: `Failed to list models: ${error instanceof Error ? error.message : String(error)}`;
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
}
);
/**
* Search for models by keywords
*/
server.registerTool(
"search",
{
title: "Search Models",
description: "Search for models on fal.ai based on keywords",
inputSchema: {
keywords: z.string().describe("The search terms to find models"),
},
},
async ({ keywords }) => {
try {
const url = `${FAL_BASE_URL}/models?keywords=${encodeURIComponent(keywords)}`;
const result = await publicRequest(url);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof FalAPIError
? error.message
: `Failed to search models: ${error instanceof Error ? error.message : String(error)}`;
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
}
);
/**
* Get model schema
*/
server.registerTool(
"schema",
{
title: "Get Model Schema",
description: "Get the OpenAPI schema for a specific fal.ai model",
inputSchema: {
model_id: z.string().describe('The ID of the model (e.g., "fal-ai/flux/dev")'),
},
},
async ({ model_id }) => {
try {
const url = `${FAL_BASE_URL}/openapi/queue/openapi.json?endpoint_id=${encodeURIComponent(model_id)}`;
const result = await publicRequest(url);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof FalAPIError
? error.message
: `Failed to get model schema: ${error instanceof Error ? error.message : String(error)}`;
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
}
);
// ============================================================================
// GENERATION TOOLS
// ============================================================================
/**
* Generate content using a fal.ai model
*/
server.registerTool(
"generate",
{
title: "Generate Content",
description: "Generate content using a fal.ai model. Supports both direct and queued execution.",
inputSchema: {
model: z.string().describe('The model ID to use (e.g., "fal-ai/flux/dev")'),
parameters: z.record(z.any()).describe("Model-specific parameters as a JSON object"),
queue: z.boolean().optional().default(false).describe("Whether to use the queuing system (default: false)"),
},
},
async ({ model, parameters, queue }) => {
try {
const sanitized = sanitizeParameters(parameters);
const url = queue ? `${FAL_QUEUE_URL}/${model}` : `${FAL_DIRECT_URL}/${model}`;
const result = await authenticatedRequest(url, config.apiKey, {
method: 'POST',
body: sanitized,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof FalAPIError
? error.message
: `Failed to generate content: ${error instanceof Error ? error.message : String(error)}`;
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
}
);
/**
* Get result from a queued request
*/
server.registerTool(
"result",
{
title: "Get Queue Result",
description: "Get the result of a queued request using the response_url",
inputSchema: {
url: z.string().describe("The response_url from a queued request"),
},
},
async ({ url }) => {
try {
const result = await authenticatedRequest(url, config.apiKey);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof FalAPIError
? error.message
: `Failed to get result: ${error instanceof Error ? error.message : String(error)}`;
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
}
);
/**
* Check status of a queued request
*/
server.registerTool(
"status",
{
title: "Check Queue Status",
description: "Check the status of a queued request using the status_url",
inputSchema: {
url: z.string().describe("The status_url from a queued request"),
},
},
async ({ url }) => {
try {
const result = await authenticatedRequest(url, config.apiKey);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof FalAPIError
? error.message
: `Failed to check status: ${error instanceof Error ? error.message : String(error)}`;
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
}
);
/**
* Cancel a queued request
*/
server.registerTool(
"cancel",
{
title: "Cancel Queue Request",
description: "Cancel a queued request using the cancel_url",
inputSchema: {
url: z.string().describe("The cancel_url from a queued request"),
},
},
async ({ url }) => {
try {
const result = await authenticatedRequest(url, config.apiKey, {
method: 'PUT',
});
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof FalAPIError
? error.message
: `Failed to cancel request: ${error instanceof Error ? error.message : String(error)}`;
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
}
);
// ============================================================================
// STORAGE TOOLS
// ============================================================================
/**
* Upload a file to fal.ai storage
*/
server.registerTool(
"upload",
{
title: "Upload File",
description: "Upload a file to fal.ai storage and get a CDN URL",
inputSchema: {
path: z.string().describe("The absolute path to the file to upload"),
},
},
async ({ path }) => {
try {
const result = await uploadFile(config.apiKey, path);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof FalAPIError
? error.message
: `Failed to upload file: ${error instanceof Error ? error.message : String(error)}`;
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
}
);
return server.server;
}
// ============================================================================
// STDIO COMPATIBILITY (Optional)
// ============================================================================
/**
* Main function for STDIO transport support
* This allows the server to run locally with STDIO transport
*/
async function main() {
// Get API key from environment variable for STDIO mode
const apiKey = process.env.FAL_KEY;
if (!apiKey) {
console.error("Error: FAL_KEY environment variable is required");
console.error("Set it with: export FAL_KEY='your-api-key-here'");
process.exit(1);
}
// Create server with configuration
const server = createServer({
config: {
apiKey,
},
});
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("fal.ai MCP Server running in STDIO mode");
}
// Run with STDIO transport when executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
}