# Directory Structure
```
├── .env.example
├── .gitignore
├── Dockerfile
├── LICENSE
├── package.json
├── README.md
├── smithery.yaml
├── src
│ ├── api
│ │ └── LottieApiClient.ts
│ ├── error
│ │ └── ErrorHandler.ts
│ ├── handlers
│ │ ├── PromptHandler.ts
│ │ ├── ResourceHandler.ts
│ │ └── ToolHandler.ts
│ ├── index.ts
│ └── types.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
LOTTIEFILES_API_URL=https://lottiefiles.com/api
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
package-lock.json
# Build output
dist/
# Environment variables
.env
# IDE files
.vscode/
.idea/
# Logs
*.log
npm-debug.log*
# OS files
.DS_Store
Thumbs.db
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# LottieFiles MCP Server
[](https://smithery.ai/server/mcp-server-lottiefiles)
A Model Context Protocol (MCP) server for searching and retrieving Lottie animations from LottieFiles.
## Features
- Search Lottie animations
- Get animation details
- Get popular animations list
## Installation
### Installing via Smithery
To install LottieFiles Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-server-lottiefiles):
```bash
npx -y smithery install mcp-server-lottiefiles --client claude
```
### Manual Installation
```bash
npm install
```
## Usage
1. Start the server:
```bash
npm start
```
2. Connect using an MCP client
## API Tools
### Search Animations
Search for Lottie animations by keywords.
Parameters:
- `query`: Search keywords
- `page`: Page number (optional, default: 1)
- `limit`: Items per page (optional, default: 20)
### Get Animation Details
Get detailed information about a specific Lottie animation.
Parameters:
- `id`: Unique identifier of the animation
### Get Popular Animations
Get a list of currently popular Lottie animations.
Parameters:
- `page`: Page number (optional, default: 1)
- `limit`: Items per page (optional, default: 20)
## Development
```bash
# Build
npm run build
```
## License
MIT
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "mcp-server-lottiefiles",
"version": "1.0.3",
"type": "module",
"main": "dist/index.js",
"bin": {
"mcp-server-lottiefiles": "dist/index.js"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"axios": "^1.7.9",
"dotenv": "^16.4.7"
},
"devDependencies": {
"@types/dotenv": "^8.2.3",
"@types/node": "^18.19.80",
"typescript": "^5.0.0"
}
}
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
startCommand:
type: stdio
configSchema:
# JSON Schema defining the configuration options for the MCP.
type: object
properties: {}
default: {}
description: No configuration needed for the LottieFiles MCP server
commandFunction:
# A JS function that produces the CLI command based on the given config to start the MCP on stdio.
|-
(config) => ({ command: 'node', args: ['dist/index.js'], env: {} })
exampleConfig: {}
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Dockerfile for LottieFiles MCP Server
FROM node:lts-alpine AS builder
WORKDIR /app
# Copy package files and typescript config
COPY package.json package-lock.json* tsconfig.json ./
# Copy source
COPY src ./src
# Install dependencies and build
RUN npm install --ignore-scripts && npm run build
# Runtime image
FROM node:lts-alpine AS runtime
WORKDIR /app
# Copy only built files and dependencies
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
# Copy package.json for completeness
COPY package.json ./
# Default command
CMD ["node", "dist/index.js"]
```
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
```typescript
export interface LottieAnimation {
id: string;
name: string;
description: string;
previewUrl: string;
downloadUrl: string;
tags: string[];
createdAt: string;
updatedAt: string;
}
export interface SearchAnimationsParams {
query: string;
page?: number;
limit?: number;
}
export interface GetAnimationParams {
id: string;
}
export interface GetPopularAnimationsParams {
page?: number;
limit?: number;
}
export interface SearchAnimationsResponse {
animations: LottieAnimation[];
total: number;
page: number;
limit: number;
}
export interface GetPopularAnimationsResponse {
animations: LottieAnimation[];
total: number;
page: number;
limit: number;
}
export interface Prompt {
name: string;
description: string;
arguments: Array<{
name: string;
description: string;
required: boolean;
}>;
}
```
--------------------------------------------------------------------------------
/src/error/ErrorHandler.ts:
--------------------------------------------------------------------------------
```typescript
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";
export class ErrorHandler {
static handleError(error: unknown): never {
if (axios.isAxiosError(error)) {
throw new McpError(
ErrorCode.InternalError,
`LottieFiles API error: ${error.response?.data?.message || error.message}`
);
}
if (error instanceof McpError) {
throw error;
}
if (error instanceof Error) {
throw new McpError(
ErrorCode.InternalError,
error.message
);
}
throw new McpError(
ErrorCode.InternalError,
'An unknown error occurred'
);
}
static validateRequiredParam(param: unknown, paramName: string): void {
if (param === undefined || param === null) {
throw new McpError(
ErrorCode.InvalidRequest,
`Missing required parameter: ${paramName}`
);
}
}
}
```
--------------------------------------------------------------------------------
/src/handlers/ResourceHandler.ts:
--------------------------------------------------------------------------------
```typescript
import { LottieApiClient } from '../api/LottieApiClient.js';
import { ListResourcesRequest, ReadResourceRequest } from '@modelcontextprotocol/sdk/types.js';
export class ResourceHandler {
constructor(private apiClient: LottieApiClient) {
this.apiClient = apiClient;
}
async listResources(request: ListResourcesRequest) {
return {
resources: [{
uri: "lottiefiles://resources/popular",
name: "popular",
mimeType: "application/json",
description: "Popular Lottie animations"
}]
};
}
async readResource(request: ReadResourceRequest) {
const { name, uri } = request.params;
switch (name) {
case "popular": {
const popularList = await this.apiClient.getPopularAnimations(
request.params.page as number,
request.params.limit as number
);
return {
contents: [{
uri,
mimeType: "application/json",
text: JSON.stringify(popularList, null, 2)
}]
};
}
default:
throw new Error(`Unknown resource: ${name}`);
}
}
}
```
--------------------------------------------------------------------------------
/src/handlers/PromptHandler.ts:
--------------------------------------------------------------------------------
```typescript
import { McpError, ErrorCode, ListPromptsRequest, GetPromptRequest } from "@modelcontextprotocol/sdk/types.js";
import { Prompt } from "../types.js";
export class PromptHandler {
async listPrompts(request: ListPromptsRequest) {
return {
prompts: [
{
name: "search_animations",
description: "Search for Lottie animations",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search keywords"
}
}
}
},
{
name: "get_popular_animations",
description: "Get popular Lottie animations",
inputSchema: {
type: "object",
properties: {}
}
}
]
};
}
async getPrompt(request: GetPromptRequest) {
const { name, arguments: args } = request.params;
switch (name) {
case "search_animations":
return {
prompt: `Please help me search for Lottie animations related to "${args?.query}".`,
tools: ["search_animations"]
};
case "get_popular_animations":
return {
prompt: "Please help me get the most popular Lottie animations.",
tools: ["get_popular_animations"]
};
default:
throw new Error(`Unknown prompt: ${name}`);
}
}
}
```
--------------------------------------------------------------------------------
/src/api/LottieApiClient.ts:
--------------------------------------------------------------------------------
```typescript
import axios from "axios";
export class LottieApiClient {
private baseUrl: string;
private axiosInstance: any;
constructor() {
this.baseUrl =
process.env.LOTTIEFILES_API_URL || "https://lottiefiles.com/api";
this.axiosInstance = axios.create({
baseURL: this.baseUrl,
params: {
format: "json",
},
});
}
async searchAnimations(query: string, page: number = 1, limit: number = 20) {
try {
const response = await this.axiosInstance.get(
`${this.baseUrl}/search/get-animations`,
{
params: {
query,
page,
limit,
},
}
);
return response.data.data.data;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to search animations: ${error.message}`);
}
throw new Error("Failed to search animations: Unknown error");
}
}
async getAnimationById(id: string) {
try {
const response = await this.axiosInstance.get(
`${this.baseUrl}/animations/get-animation-data`,
{
params: {
fileId: id,
},
}
);
return response.data;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to get animation: ${error.message}`);
}
throw new Error("Failed to get animation: Unknown error");
}
}
async getAnimationByUser(user: string, page: number = 1, limit: number = 20) {
try {
const response = await this.axiosInstance.get(
`${this.baseUrl}/search/get-users`,
{
params: {
query: user,
page,
limit,
},
}
);
return response.data;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to get animation: ${error.message}`);
}
throw new Error("Failed to get animation: Unknown error");
}
}
async getPopularAnimations(page: number = 1, limit: number = 20) {
try {
const response = await this.axiosInstance.get(
`${this.baseUrl}/iconscout/popular-animations-weekly?api=%26sort%3Dpopular`,
{
params: {
page,
limit,
},
}
);
return response.data.popularWeeklyData.data;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to get popular animations: ${error.message}`);
}
throw new Error("Failed to get popular animations: Unknown error");
}
}
}
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
ListToolsRequestSchema,
CallToolRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import dotenv from "dotenv";
import { LottieApiClient } from "./api/LottieApiClient.js";
import { ToolHandler } from "./handlers/ToolHandler.js";
import { ResourceHandler } from "./handlers/ResourceHandler.js";
import { PromptHandler } from "./handlers/PromptHandler.js";
import { ErrorHandler } from "./error/ErrorHandler.js";
class LottieServer {
private readonly server: Server;
private readonly apiClient: LottieApiClient;
private readonly toolHandler: ToolHandler;
private readonly resourceHandler: ResourceHandler;
private readonly promptHandler: PromptHandler;
constructor() {
// Load environment variables
dotenv.config();
// Initialize API client
this.apiClient = new LottieApiClient();
// Initialize handlers
this.toolHandler = new ToolHandler(this.apiClient);
this.resourceHandler = new ResourceHandler(this.apiClient);
this.promptHandler = new PromptHandler();
// Initialize server with configuration
this.server = this.initializeServer();
// Setup handlers and error handling
this.setupHandlers();
this.setupErrorHandling();
}
private initializeServer(): Server {
return new Server(
{
name: "lottiefiles-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
resources: {
list: true,
read: true,
subscribe: false,
},
prompts: {
list: true,
get: true,
},
},
}
);
}
private setupErrorHandling(): void {
this.server.onerror = (error) => {
console.error("[MCP Error]", error);
};
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
private setupHandlers(): void {
// Tool handlers
this.server.setRequestHandler(ListToolsRequestSchema, async (request) => {
return await this.toolHandler.listTools(request);
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
return await this.toolHandler.callTool(request);
});
// Resource handlers
this.server.setRequestHandler(
ListResourcesRequestSchema,
async (request) => {
return await this.resourceHandler.listResources(request);
}
);
this.server.setRequestHandler(
ReadResourceRequestSchema,
async (request) => {
return await this.resourceHandler.readResource(request);
}
);
// Prompt handlers
this.server.setRequestHandler(ListPromptsRequestSchema, async (request) => {
return await this.promptHandler.listPrompts(request);
});
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
return await this.promptHandler.getPrompt(request);
});
}
async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}
// Start the server
const server = new LottieServer();
server.run().catch((error) => {
ErrorHandler.handleError(error);
});
```
--------------------------------------------------------------------------------
/src/handlers/ToolHandler.ts:
--------------------------------------------------------------------------------
```typescript
import {
CallToolRequest,
ListToolsRequest,
} from "@modelcontextprotocol/sdk/types.js";
import { LottieApiClient } from "../api/LottieApiClient.js";
export class ToolHandler {
constructor(private apiClient: LottieApiClient) {
this.apiClient = apiClient;
}
async listTools(request: ListToolsRequest) {
return {
tools: [
{
name: "search_animations",
description:
"Search for Lottie animations by keywords, tags, and other criteria. Supports pagination.",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description:
"Search keywords that match animation names, descriptions, tags, etc.",
},
page: {
type: "integer",
description: "Page number, starting from 1",
minimum: 1,
default: 1,
},
limit: {
type: "integer",
description: "Number of items per page",
minimum: 1,
maximum: 100,
default: 20,
},
},
},
},
{
name: "get_animation_details",
description:
"Get detailed information about a specific Lottie animation, including animation data, preview images, and tags.",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "Unique identifier of the animation",
},
},
required: ["id"],
},
},
{
name: "get_popular_animations",
description: "Get a list of currently popular Lottie animations.",
inputSchema: {
type: "object",
properties: {
page: {
type: "integer",
description: "Page number, starting from 1",
minimum: 1,
default: 1,
},
limit: {
type: "integer",
description: "Number of items per page",
minimum: 1,
maximum: 100,
default: 20,
},
},
},
},
],
};
}
async callTool(request: CallToolRequest) {
const { name, arguments: args } = request.params;
switch (name) {
case "search_animations":
const list = await this.apiClient.searchAnimations(
args?.query as string,
args?.page as number,
args?.limit as number
);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
count: list.length,
animations: list,
},
null,
2
),
},
],
};
case "get_animation_details":
const details = await this.apiClient.getAnimationById(
args?.id as string
);
return {
content: [
{
type: "text",
text: JSON.stringify(details, null, 2),
},
],
};
case "get_popular_animations":
const popular = await this.apiClient.getPopularAnimations(
args?.page as number,
args?.limit as number
);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
count: popular.length,
popular: popular,
},
null,
2
),
},
],
};
default:
throw new Error(`Unknown tool: ${name}`);
}
}
}
```