#
tokens: 5233/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── api
│   ├── __init__.py
│   ├── config.py
│   ├── generate.py
│   ├── models.py
│   ├── storage.py
│   └── utils.py
├── LICENSE
├── main.py
└── README.md
```

# Files

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # fal.ai MCP Server
 2 | 
 3 | A Model Context Protocol (MCP) server for interacting with fal.ai models and services.
 4 | 
 5 | ## Features
 6 | 
 7 | - List all available fal.ai models
 8 | - Search for specific models by keywords
 9 | - Get model schemas
10 | - Generate content using any fal.ai model
11 | - Support for both direct and queued model execution
12 | - Queue management (status checking, getting results, cancelling requests)
13 | - File upload to fal.ai CDN
14 | 
15 | ## Requirements
16 | 
17 | - Python 3.10+
18 | - fastmcp
19 | - httpx
20 | - aiofiles
21 | - A fal.ai API key
22 | 
23 | ## Installation
24 | 
25 | 1. Clone this repository:
26 | ```bash
27 | git clone https://github.com/am0y/mcp-fal.git
28 | cd mcp-fal
29 | ```
30 | 
31 | 2. Install the required packages:
32 | ```bash
33 | pip install fastmcp httpx aiofiles
34 | ```
35 | 
36 | 3. Set your fal.ai API key as an environment variable:
37 | ```bash
38 | export FAL_KEY="YOUR_FAL_API_KEY_HERE"
39 | ```
40 | 
41 | ## Usage
42 | 
43 | ### Running the Server
44 | 
45 | You can run the server in development mode with:
46 | 
47 | ```bash
48 | fastmcp dev main.py
49 | ```
50 | 
51 | This will launch the MCP Inspector web interface where you can test the tools interactively.
52 | 
53 | ### Installing in Claude Desktop
54 | 
55 | To use the server with Claude Desktop:
56 | 
57 | ```bash
58 | fastmcp install main.py -e FAL_KEY="YOUR_FAL_API_KEY_HERE"
59 | ```
60 | 
61 | This will make the server available to Claude in the Desktop app.
62 | 
63 | ### Running Directly
64 | 
65 | You can also run the server directly:
66 | 
67 | ```bash
68 | python main.py
69 | ```
70 | 
71 | ## API Reference
72 | 
73 | ### Tools
74 | 
75 | - `models(page=None, total=None)` - List available models with optional pagination
76 | - `search(keywords)` - Search for models by keywords
77 | - `schema(model_id)` - Get OpenAPI schema for a specific model
78 | - `generate(model, parameters, queue=False)` - Generate content using a model
79 | - `result(url)` - Get result from a queued request
80 | - `status(url)` - Check status of a queued request
81 | - `cancel(url)` - Cancel a queued request
82 | - `upload(path` - Upload a file to fal.ai CDN
83 | 
84 | ## License
85 | 
86 | [MIT](LICENSE)
```

--------------------------------------------------------------------------------
/api/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """
2 | fal.ai MCP Server API package.
3 | 
4 | This package contains modules for interacting with fal.ai models and services through the Model Context Protocol (MCP).
5 | """
6 | 
7 | __version__ = "1.0.0"
```

--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | fal.ai MCP Server : Main entry point
 3 | 
 4 | This module sets up and runs the fal.ai MCP server,
 5 | providing tools to interact with fal.ai models and services.
 6 | """
 7 | 
 8 | import os
 9 | import sys
10 | from fastmcp import FastMCP
11 | from api.models import register_model_tools
12 | from api.generate import register_generation_tools
13 | from api.storage import register_storage_tools
14 | from api.config import get_api_key, SERVER_NAME, SERVER_DESCRIPTION, SERVER_VERSION, SERVER_DEPENDENCIES
15 | 
16 | mcp = FastMCP(
17 |     SERVER_NAME,
18 |     description=SERVER_DESCRIPTION,
19 |     dependencies=SERVER_DEPENDENCIES,
20 |     version=SERVER_VERSION
21 | )
22 | 
23 | register_model_tools(mcp)
24 | register_generation_tools(mcp)
25 | register_storage_tools(mcp)
26 | 
27 | def main():
28 |     try:
29 |         get_api_key()
30 |     except ValueError:
31 |         pass
32 |     
33 |     try:
34 |         mcp.run()
35 |     except Exception as e:
36 |         sys.exit(1)
37 | 
38 | if __name__ == "__main__":
39 |     main()
```

--------------------------------------------------------------------------------
/api/config.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Configuration module for the fal.ai MCP server.
 3 | 
 4 | This module provides centralized configuration settings for the fal.ai MCP server, including API endpoints, timeouts, and more.
 5 | """
 6 | 
 7 | import os
 8 | from typing import Dict, Any, Optional
 9 | 
10 | FAL_BASE_URL = "https://fal.ai/api"
11 | FAL_QUEUE_URL = "https://queue.fal.run"
12 | FAL_DIRECT_URL = "https://fal.run"
13 | FAL_REST_URL = "https://rest.alpha.fal.ai"
14 | 
15 | DEFAULT_TIMEOUT = 30.0
16 | AUTHENTICATED_TIMEOUT = 100.0
17 | 
18 | API_KEY_ENV_VAR = "FAL_KEY"
19 | 
20 | SERVER_NAME = "fal.ai MCP Server"
21 | SERVER_DESCRIPTION = "Access fal.ai models and generate content through MCP"
22 | SERVER_VERSION = "1.0.0"
23 | SERVER_DEPENDENCIES = ["httpx", "aiofiles"]
24 | 
25 | 
26 | def get_env(key: str, default: Optional[str] = None) -> Optional[str]:
27 |     """
28 |     Get an environment variable with optional default value.
29 |     
30 |     Args:
31 |         key: The name of the environment variable
32 |         default: Optional default value if not found
33 |         
34 |     Returns:
35 |         The value of the environment variable or the default
36 |     """
37 |     return os.environ.get(key, default)
38 | 
39 | def get_api_key() -> str:
40 |     """
41 |     Get the fal.ai API key from environment variables.
42 |     
43 |     Returns:
44 |         The API key as a string
45 |         
46 |     Raises:
47 |         ValueError: If the FAL_KEY environment variable is not set
48 |     """
49 |     api_key = get_env(API_KEY_ENV_VAR)
50 |     if not api_key:
51 |         raise ValueError(f"{API_KEY_ENV_VAR} environment variable not set")
52 |     return api_key
```

--------------------------------------------------------------------------------
/api/models.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Models module for fal.ai MCP server.
 3 | 
 4 | This module provides tools for listing, searching,
 5 | and retrieving schemas for fal.ai models.
 6 | """
 7 | 
 8 | from typing import Optional, Dict, Any, List
 9 | from fastmcp import FastMCP
10 | from .utils import public_request
11 | from .config import FAL_BASE_URL
12 | 
13 | def register_model_tools(mcp: FastMCP):
14 |     """Register model-related tools with the MCP server."""
15 |     
16 |     @mcp.tool()
17 |     async def models(page: Optional[int] = None, total: Optional[int] = None) -> List[Dict[str, Any]]:
18 |         """
19 |         List available models on fal.ai. Ensure to use the total and page arguments. Avoid listing all the models at once.
20 |         
21 |         Args:
22 |             page: The page number of models to retrieve (pagination)
23 |             total: The total number of models to retrieve per page
24 |             
25 |         Returns:
26 |             A list of models with their metadata
27 |         """
28 |         url = f"{FAL_BASE_URL}/models"
29 |         
30 |         params = {}
31 |         if page is not None:
32 |             params["page"] = page
33 |         if total is not None:
34 |             params["total"] = total
35 |         
36 |         if params:
37 |             url += "?" + "&".join(f"{k}={v}" for k, v in params.items())
38 |         
39 |         result = await public_request(url)
40 |         
41 |         return result
42 | 
43 |     @mcp.tool()
44 |     async def search(keywords: str) -> List[Dict[str, Any]]:
45 |         """
46 |         Search for models on fal.ai based on keywords.
47 |         
48 |         Args:
49 |             keywords: The search terms to find models
50 |             
51 |         Returns:
52 |             A list of models matching the search criteria
53 |         """
54 |         if not isinstance(keywords, str):
55 |             keywords = str(keywords)
56 |         
57 |         url = f"{FAL_BASE_URL}/models?keywords={keywords}"
58 |         
59 |         result = await public_request(url)
60 |         
61 |         return result
62 | 
63 |     @mcp.tool()
64 |     async def schema(model_id: str) -> Dict[str, Any]:
65 |         """
66 |         Get the OpenAPI schema for a specific model.
67 |         
68 |         Args:
69 |             model_id: The ID of the model (e.g., "fal-ai/flux/dev")
70 |             
71 |         Returns:
72 |             The OpenAPI schema for the model
73 |         """
74 |         if not isinstance(model_id, str):
75 |             model_id = str(model_id)
76 |             
77 |         url = f"{FAL_BASE_URL}/openapi/queue/openapi.json?endpoint_id={model_id}"
78 |         
79 |         return await public_request(url)
```

--------------------------------------------------------------------------------
/api/storage.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Storage module for fal.ai MCP server.
 3 | 
 4 | This module provides tools for uploading files to fal.ai storage.
 5 | """
 6 | 
 7 | import os
 8 | import mimetypes
 9 | import aiofiles
10 | from typing import Dict, Any, Optional
11 | from fastmcp import FastMCP
12 | from .utils import authenticated_request, FalAPIError
13 | from .config import FAL_REST_URL
14 | 
15 | def register_storage_tools(mcp: FastMCP):
16 |     """Register storage-related tools with the MCP server."""
17 |     
18 |     @mcp.tool()
19 |     async def upload(path: str) -> Dict[str, Any]:
20 |         """
21 |         Upload a file to fal.ai storage.
22 |         
23 |         Args:
24 |             path: The absolute path to the file to upload
25 |             
26 |         Returns:
27 |             Information about the uploaded file, including the file_url
28 |         """
29 |         if not os.path.exists(path):
30 |             raise FileNotFoundError(f"File not found: {path}")
31 |             
32 |         filename = os.path.basename(path)
33 |         file_size = os.path.getsize(path)
34 |         
35 |         content_type = mimetypes.guess_type(path)[0]
36 |         if not content_type:
37 |             content_type = "application/octet-stream"
38 |         
39 |         initiate_url = f"{FAL_REST_URL}/storage/upload/initiate?storage_type=fal-cdn-v3"
40 |         initiate_payload = {
41 |             "content_type": content_type,
42 |             "file_name": filename
43 |         }
44 |         
45 |         try:
46 |             initiate_response = await authenticated_request(
47 |                 url=initiate_url, 
48 |                 method="POST",
49 |                 json_data=initiate_payload
50 |             )
51 |             
52 |             file_url = initiate_response["file_url"]
53 |             upload_url = initiate_response["upload_url"]
54 |             
55 |             async with aiofiles.open(path, "rb") as file:
56 |                 file_content = await file.read()
57 |                 
58 |                 import httpx
59 |                 async with httpx.AsyncClient() as client:
60 |                     upload_response = await client.put(
61 |                         upload_url,
62 |                         content=file_content,
63 |                         headers={"Content-Type": content_type}
64 |                     )
65 |                     upload_response.raise_for_status()
66 |             
67 |             return {
68 |                 "file_url": file_url,
69 |                 "file_name": filename,
70 |                 "file_size": file_size,
71 |                 "content_type": content_type
72 |             }
73 |             
74 |         except FalAPIError as e:
75 |             raise
```

--------------------------------------------------------------------------------
/api/generate.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Generate module for fal.ai MCP server.
  3 | 
  4 | This module provides tools for generating content
  5 | and managing queue operations with fal.ai models.
  6 | """
  7 | 
  8 | from typing import Dict, Any, Optional
  9 | from fastmcp import FastMCP
 10 | from .utils import authenticated_request, sanitize_parameters, FalAPIError
 11 | from .config import FAL_QUEUE_URL, FAL_DIRECT_URL
 12 | 
 13 | def register_generation_tools(mcp: FastMCP):
 14 |     """Register generation-related tools with the MCP server."""
 15 |     
 16 |     @mcp.tool()
 17 |     async def generate(model: str, parameters: Dict[str, Any], queue: bool = False) -> Dict[str, Any]:
 18 |         """
 19 |         Generate content using a fal.ai model.
 20 |         
 21 |         Args:
 22 |             model: The model ID to use (e.g., "fal-ai/flux/dev")
 23 |             parameters: Model-specific parameters as a dictionary
 24 |             queue: Whether to use the queuing system (default: False)
 25 |             
 26 |         Returns:
 27 |             The model's response
 28 |         """
 29 |         if not isinstance(model, str):
 30 |             model = str(model)
 31 |             
 32 |         sanitized_parameters = sanitize_parameters(parameters)
 33 |         
 34 |         try:
 35 |             if queue:
 36 |                 url = f"{FAL_QUEUE_URL}/{model}"
 37 |             else:
 38 |                 url = f"{FAL_DIRECT_URL}/{model}"
 39 |             
 40 |             result = await authenticated_request(url, method="POST", json_data=sanitized_parameters)
 41 |             
 42 |             return result
 43 |             
 44 |         except FalAPIError as e:
 45 |             raise
 46 | 
 47 |     @mcp.tool()
 48 |     async def result(url: str) -> Dict[str, Any]:
 49 |         """
 50 |         Get the result of a queued request.
 51 |         
 52 |         Args:
 53 |             url: The response_url from a queued request
 54 |             
 55 |         Returns:
 56 |             The generation result
 57 |         """
 58 |         if not isinstance(url, str):
 59 |             url = str(url)
 60 |         
 61 |         try:
 62 |             result = await authenticated_request(url)
 63 |             
 64 |             return result
 65 |             
 66 |         except FalAPIError as e:
 67 |             raise
 68 | 
 69 |     @mcp.tool()
 70 |     async def status(url: str) -> Dict[str, Any]:
 71 |         """
 72 |         Check the status of a queued request.
 73 |         
 74 |         Args:
 75 |             url: The status_url from a queued request
 76 |             
 77 |         Returns:
 78 |             The current status of the queued request
 79 |         """
 80 |         if not isinstance(url, str):
 81 |             url = str(url)
 82 |         
 83 |         try:
 84 |             result = await authenticated_request(url)
 85 |             
 86 |             return result
 87 |             
 88 |         except FalAPIError as e:
 89 |             raise
 90 | 
 91 |     @mcp.tool()
 92 |     async def cancel(url: str) -> Dict[str, Any]:
 93 |         """
 94 |         Cancel a queued request.
 95 |         
 96 |         Args:
 97 |             url: The cancel_url from a queued request
 98 |             
 99 |         Returns:
100 |             The result of the cancellation attempt
101 |         """
102 |         if not isinstance(url, str):
103 |             url = str(url)
104 |         
105 |         try:
106 |             result = await authenticated_request(url, method="PUT")
107 |             
108 |             return result
109 |             
110 |         except FalAPIError as e:
111 |             raise
```

--------------------------------------------------------------------------------
/api/utils.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Utility functions for the fal.ai MCP server.
  3 | 
  4 | This module provides helper functions for making API requests
  5 | and handling authentication with the fal.ai service.
  6 | """
  7 | 
  8 | import httpx
  9 | import json
 10 | from typing import Optional, Dict, Any, Union
 11 | from .config import get_api_key, DEFAULT_TIMEOUT, AUTHENTICATED_TIMEOUT
 12 | 
 13 | class FalAPIError(Exception):
 14 |     """Exception raised for errors in the fal.ai API responses."""
 15 |     def __init__(self, message: str, status_code: Optional[int] = None, details: Optional[Dict[str, Any]] = None):
 16 |         """
 17 |         Initialize the exception with error details.
 18 |         
 19 |         Args:
 20 |             message: The error message
 21 |             status_code: Optional HTTP status code
 22 |             details: Optional error details as a dictionary
 23 |         """
 24 |         self.status_code = status_code
 25 |         self.details = details or {}
 26 |         error_msg = message
 27 |         if status_code:
 28 |             error_msg = f"[{status_code}] {error_msg}"
 29 |         super().__init__(error_msg)
 30 | 
 31 | async def authenticated_request(
 32 |     url: str, 
 33 |     method: str = "GET", 
 34 |     json_data: Optional[Dict[str, Any]] = None,
 35 |     timeout: float = AUTHENTICATED_TIMEOUT
 36 | ) -> Dict[str, Any]:
 37 |     """
 38 |     Make an authenticated request to fal.ai API.
 39 |     
 40 |     Args:
 41 |         url: The URL to make the request to
 42 |         method: The HTTP method to use (GET, POST, PUT)
 43 |         json_data: Optional JSON data to send with the request
 44 |         timeout: Request timeout in seconds
 45 |         
 46 |     Returns:
 47 |         The JSON response from the API
 48 |         
 49 |     Raises:
 50 |         FalAPIError: If the API returns an error
 51 |         httpx.HTTPStatusError: If the HTTP request fails
 52 |     """
 53 |     try:
 54 |         headers = {"Authorization": f"Key {get_api_key()}"}
 55 |         
 56 |         async with httpx.AsyncClient() as client:
 57 |             if method == "GET":
 58 |                 response = await client.get(url, headers=headers, timeout=timeout)
 59 |             elif method == "POST":
 60 |                 response = await client.post(url, headers=headers, json=json_data, timeout=timeout)
 61 |             elif method == "PUT":
 62 |                 response = await client.put(url, headers=headers, json=json_data, timeout=timeout)
 63 |             else:
 64 |                 raise ValueError(f"Unsupported HTTP method: {method}")
 65 |             
 66 |             try:
 67 |                 response.raise_for_status()
 68 |             except httpx.HTTPStatusError as e:
 69 |                 try:
 70 |                     error_details = json.loads(e.response.text)
 71 |                     raise FalAPIError(
 72 |                         f"API error: {error_details}",
 73 |                         status_code=e.response.status_code,
 74 |                         details=error_details
 75 |                     )
 76 |                 except json.JSONDecodeError:
 77 |                     raise FalAPIError(
 78 |                         f"API error: {e.response.text}",
 79 |                         status_code=e.response.status_code
 80 |                     )
 81 |             
 82 |             return response.json()
 83 |             
 84 |     except httpx.RequestError as e:
 85 |         raise FalAPIError(f"Request failed: {str(e)}")
 86 | 
 87 | async def public_request(url: str, timeout: float = DEFAULT_TIMEOUT) -> Dict[str, Any]:
 88 |     """
 89 |     Make a non-authenticated request to fal.ai API.
 90 |     
 91 |     Args:
 92 |         url: The URL to make the request to
 93 |         timeout: Request timeout in seconds
 94 |         
 95 |     Returns:
 96 |         The JSON response from the API
 97 |         
 98 |     Raises:
 99 |         FalAPIError: If the API returns an error
100 |         httpx.HTTPStatusError: If the HTTP request fails
101 |     """
102 |     try:
103 |         async with httpx.AsyncClient() as client:
104 |             response = await client.get(url, timeout=timeout)
105 |             
106 |             try:
107 |                 response.raise_for_status()
108 |             except httpx.HTTPStatusError as e:
109 |                 try:
110 |                     error_details = json.loads(e.response.text)
111 |                     raise FalAPIError(
112 |                         f"API error: {error_details}",
113 |                         status_code=e.response.status_code,
114 |                         details=error_details
115 |                     )
116 |                 except json.JSONDecodeError:
117 |                     raise FalAPIError(
118 |                         f"API error: {e.response.text}",
119 |                         status_code=e.response.status_code
120 |                     )
121 |             
122 |             return response.json()
123 |             
124 |     except httpx.RequestError as e:
125 |         raise FalAPIError(f"Request failed: {str(e)}")
126 | 
127 | def sanitize_parameters(parameters: Dict[str, Any]) -> Dict[str, Any]:
128 |     """
129 |     Sanitize parameters for API requests.
130 |     
131 |     Args:
132 |         parameters: The parameters to sanitize
133 |         
134 |     Returns:
135 |         Sanitized parameters
136 |     """
137 |     sanitized = parameters.copy()
138 |     sanitized = {k: v for k, v in sanitized.items() if v is not None}
139 |     
140 |     return sanitized
```