# 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:
--------------------------------------------------------------------------------
```
1 | LOTTIEFILES_API_URL=https://lottiefiles.com/api
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Dependencies
2 | node_modules/
3 | package-lock.json
4 |
5 | # Build output
6 | dist/
7 |
8 | # Environment variables
9 | .env
10 |
11 | # IDE files
12 | .vscode/
13 | .idea/
14 |
15 | # Logs
16 | *.log
17 | npm-debug.log*
18 |
19 | # OS files
20 | .DS_Store
21 | Thumbs.db
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # LottieFiles MCP Server
2 | [](https://smithery.ai/server/mcp-server-lottiefiles)
3 |
4 | A Model Context Protocol (MCP) server for searching and retrieving Lottie animations from LottieFiles.
5 |
6 | ## Features
7 |
8 | - Search Lottie animations
9 | - Get animation details
10 | - Get popular animations list
11 |
12 | ## Installation
13 |
14 | ### Installing via Smithery
15 |
16 | To install LottieFiles Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-server-lottiefiles):
17 |
18 | ```bash
19 | npx -y smithery install mcp-server-lottiefiles --client claude
20 | ```
21 |
22 | ### Manual Installation
23 | ```bash
24 | npm install
25 | ```
26 |
27 | ## Usage
28 |
29 | 1. Start the server:
30 |
31 | ```bash
32 | npm start
33 | ```
34 |
35 | 2. Connect using an MCP client
36 |
37 | ## API Tools
38 |
39 | ### Search Animations
40 |
41 | Search for Lottie animations by keywords.
42 |
43 | Parameters:
44 | - `query`: Search keywords
45 | - `page`: Page number (optional, default: 1)
46 | - `limit`: Items per page (optional, default: 20)
47 |
48 | ### Get Animation Details
49 |
50 | Get detailed information about a specific Lottie animation.
51 |
52 | Parameters:
53 | - `id`: Unique identifier of the animation
54 |
55 | ### Get Popular Animations
56 |
57 | Get a list of currently popular Lottie animations.
58 |
59 | Parameters:
60 | - `page`: Page number (optional, default: 1)
61 | - `limit`: Items per page (optional, default: 20)
62 |
63 | ## Development
64 |
65 | ```bash
66 | # Build
67 | npm run build
68 | ```
69 |
70 | ## License
71 |
72 | MIT
73 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "ESNext",
5 | "moduleResolution": "node",
6 | "esModuleInterop": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "outDir": "./dist",
11 | "rootDir": "./src",
12 | "declaration": true
13 | },
14 | "include": ["src/**/*"],
15 | "exclude": ["node_modules", "dist"]
16 | }
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "mcp-server-lottiefiles",
3 | "version": "1.0.3",
4 | "type": "module",
5 | "main": "dist/index.js",
6 | "bin": {
7 | "mcp-server-lottiefiles": "dist/index.js"
8 | },
9 | "scripts": {
10 | "build": "tsc",
11 | "start": "node dist/index.js"
12 | },
13 | "dependencies": {
14 | "@modelcontextprotocol/sdk": "^1.7.0",
15 | "axios": "^1.7.9",
16 | "dotenv": "^16.4.7"
17 | },
18 | "devDependencies": {
19 | "@types/dotenv": "^8.2.3",
20 | "@types/node": "^18.19.80",
21 | "typescript": "^5.0.0"
22 | }
23 | }
24 |
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
2 |
3 | startCommand:
4 | type: stdio
5 | configSchema:
6 | # JSON Schema defining the configuration options for the MCP.
7 | type: object
8 | properties: {}
9 | default: {}
10 | description: No configuration needed for the LottieFiles MCP server
11 | commandFunction:
12 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
13 | |-
14 | (config) => ({ command: 'node', args: ['dist/index.js'], env: {} })
15 | exampleConfig: {}
16 |
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
2 | # Dockerfile for LottieFiles MCP Server
3 | FROM node:lts-alpine AS builder
4 | WORKDIR /app
5 |
6 | # Copy package files and typescript config
7 | COPY package.json package-lock.json* tsconfig.json ./
8 |
9 | # Copy source
10 | COPY src ./src
11 |
12 | # Install dependencies and build
13 | RUN npm install --ignore-scripts && npm run build
14 |
15 | # Runtime image
16 | FROM node:lts-alpine AS runtime
17 | WORKDIR /app
18 |
19 | # Copy only built files and dependencies
20 | COPY --from=builder /app/dist ./dist
21 | COPY --from=builder /app/node_modules ./node_modules
22 |
23 | # Copy package.json for completeness
24 | COPY package.json ./
25 |
26 | # Default command
27 | CMD ["node", "dist/index.js"]
28 |
```
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | export interface LottieAnimation {
2 | id: string;
3 | name: string;
4 | description: string;
5 | previewUrl: string;
6 | downloadUrl: string;
7 | tags: string[];
8 | createdAt: string;
9 | updatedAt: string;
10 | }
11 |
12 | export interface SearchAnimationsParams {
13 | query: string;
14 | page?: number;
15 | limit?: number;
16 | }
17 |
18 | export interface GetAnimationParams {
19 | id: string;
20 | }
21 |
22 | export interface GetPopularAnimationsParams {
23 | page?: number;
24 | limit?: number;
25 | }
26 |
27 | export interface SearchAnimationsResponse {
28 | animations: LottieAnimation[];
29 | total: number;
30 | page: number;
31 | limit: number;
32 | }
33 |
34 | export interface GetPopularAnimationsResponse {
35 | animations: LottieAnimation[];
36 | total: number;
37 | page: number;
38 | limit: number;
39 | }
40 |
41 | export interface Prompt {
42 | name: string;
43 | description: string;
44 | arguments: Array<{
45 | name: string;
46 | description: string;
47 | required: boolean;
48 | }>;
49 | }
```
--------------------------------------------------------------------------------
/src/error/ErrorHandler.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
2 | import axios from "axios";
3 |
4 | export class ErrorHandler {
5 | static handleError(error: unknown): never {
6 | if (axios.isAxiosError(error)) {
7 | throw new McpError(
8 | ErrorCode.InternalError,
9 | `LottieFiles API error: ${error.response?.data?.message || error.message}`
10 | );
11 | }
12 |
13 | if (error instanceof McpError) {
14 | throw error;
15 | }
16 |
17 | if (error instanceof Error) {
18 | throw new McpError(
19 | ErrorCode.InternalError,
20 | error.message
21 | );
22 | }
23 |
24 | throw new McpError(
25 | ErrorCode.InternalError,
26 | 'An unknown error occurred'
27 | );
28 | }
29 |
30 | static validateRequiredParam(param: unknown, paramName: string): void {
31 | if (param === undefined || param === null) {
32 | throw new McpError(
33 | ErrorCode.InvalidRequest,
34 | `Missing required parameter: ${paramName}`
35 | );
36 | }
37 | }
38 | }
```
--------------------------------------------------------------------------------
/src/handlers/ResourceHandler.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { LottieApiClient } from '../api/LottieApiClient.js';
2 | import { ListResourcesRequest, ReadResourceRequest } from '@modelcontextprotocol/sdk/types.js';
3 |
4 | export class ResourceHandler {
5 | constructor(private apiClient: LottieApiClient) {
6 | this.apiClient = apiClient;
7 | }
8 |
9 | async listResources(request: ListResourcesRequest) {
10 | return {
11 | resources: [{
12 | uri: "lottiefiles://resources/popular",
13 | name: "popular",
14 | mimeType: "application/json",
15 | description: "Popular Lottie animations"
16 | }]
17 | };
18 | }
19 |
20 | async readResource(request: ReadResourceRequest) {
21 | const { name, uri } = request.params;
22 |
23 | switch (name) {
24 | case "popular": {
25 | const popularList = await this.apiClient.getPopularAnimations(
26 | request.params.page as number,
27 | request.params.limit as number
28 | );
29 | return {
30 | contents: [{
31 | uri,
32 | mimeType: "application/json",
33 | text: JSON.stringify(popularList, null, 2)
34 | }]
35 | };
36 | }
37 |
38 | default:
39 | throw new Error(`Unknown resource: ${name}`);
40 | }
41 | }
42 | }
```
--------------------------------------------------------------------------------
/src/handlers/PromptHandler.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpError, ErrorCode, ListPromptsRequest, GetPromptRequest } from "@modelcontextprotocol/sdk/types.js";
2 | import { Prompt } from "../types.js";
3 |
4 | export class PromptHandler {
5 |
6 | async listPrompts(request: ListPromptsRequest) {
7 | return {
8 | prompts: [
9 | {
10 | name: "search_animations",
11 | description: "Search for Lottie animations",
12 | inputSchema: {
13 | type: "object",
14 | properties: {
15 | query: {
16 | type: "string",
17 | description: "Search keywords"
18 | }
19 | }
20 | }
21 | },
22 | {
23 | name: "get_popular_animations",
24 | description: "Get popular Lottie animations",
25 | inputSchema: {
26 | type: "object",
27 | properties: {}
28 | }
29 | }
30 | ]
31 | };
32 | }
33 |
34 | async getPrompt(request: GetPromptRequest) {
35 | const { name, arguments: args } = request.params;
36 |
37 | switch (name) {
38 | case "search_animations":
39 | return {
40 | prompt: `Please help me search for Lottie animations related to "${args?.query}".`,
41 | tools: ["search_animations"]
42 | };
43 |
44 | case "get_popular_animations":
45 | return {
46 | prompt: "Please help me get the most popular Lottie animations.",
47 | tools: ["get_popular_animations"]
48 | };
49 |
50 | default:
51 | throw new Error(`Unknown prompt: ${name}`);
52 | }
53 | }
54 |
55 | }
```
--------------------------------------------------------------------------------
/src/api/LottieApiClient.ts:
--------------------------------------------------------------------------------
```typescript
1 | import axios from "axios";
2 | export class LottieApiClient {
3 | private baseUrl: string;
4 | private axiosInstance: any;
5 |
6 | constructor() {
7 | this.baseUrl =
8 | process.env.LOTTIEFILES_API_URL || "https://lottiefiles.com/api";
9 | this.axiosInstance = axios.create({
10 | baseURL: this.baseUrl,
11 | params: {
12 | format: "json",
13 | },
14 | });
15 | }
16 |
17 | async searchAnimations(query: string, page: number = 1, limit: number = 20) {
18 | try {
19 | const response = await this.axiosInstance.get(
20 | `${this.baseUrl}/search/get-animations`,
21 | {
22 | params: {
23 | query,
24 | page,
25 | limit,
26 | },
27 | }
28 | );
29 | return response.data.data.data;
30 | } catch (error) {
31 | if (error instanceof Error) {
32 | throw new Error(`Failed to search animations: ${error.message}`);
33 | }
34 | throw new Error("Failed to search animations: Unknown error");
35 | }
36 | }
37 |
38 | async getAnimationById(id: string) {
39 | try {
40 | const response = await this.axiosInstance.get(
41 | `${this.baseUrl}/animations/get-animation-data`,
42 | {
43 | params: {
44 | fileId: id,
45 | },
46 | }
47 | );
48 |
49 | return response.data;
50 | } catch (error) {
51 | if (error instanceof Error) {
52 | throw new Error(`Failed to get animation: ${error.message}`);
53 | }
54 | throw new Error("Failed to get animation: Unknown error");
55 | }
56 | }
57 |
58 | async getAnimationByUser(user: string, page: number = 1, limit: number = 20) {
59 | try {
60 | const response = await this.axiosInstance.get(
61 | `${this.baseUrl}/search/get-users`,
62 | {
63 | params: {
64 | query: user,
65 | page,
66 | limit,
67 | },
68 | }
69 | );
70 | return response.data;
71 | } catch (error) {
72 | if (error instanceof Error) {
73 | throw new Error(`Failed to get animation: ${error.message}`);
74 | }
75 | throw new Error("Failed to get animation: Unknown error");
76 | }
77 | }
78 |
79 | async getPopularAnimations(page: number = 1, limit: number = 20) {
80 | try {
81 | const response = await this.axiosInstance.get(
82 | `${this.baseUrl}/iconscout/popular-animations-weekly?api=%26sort%3Dpopular`,
83 | {
84 | params: {
85 | page,
86 | limit,
87 | },
88 | }
89 | );
90 |
91 | return response.data.popularWeeklyData.data;
92 | } catch (error) {
93 | if (error instanceof Error) {
94 | throw new Error(`Failed to get popular animations: ${error.message}`);
95 | }
96 | throw new Error("Failed to get popular animations: Unknown error");
97 | }
98 | }
99 | }
100 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3 | import {
4 | ListToolsRequestSchema,
5 | CallToolRequestSchema,
6 | ListResourcesRequestSchema,
7 | ReadResourceRequestSchema,
8 | ListPromptsRequestSchema,
9 | GetPromptRequestSchema,
10 | } from "@modelcontextprotocol/sdk/types.js";
11 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12 | import dotenv from "dotenv";
13 |
14 | import { LottieApiClient } from "./api/LottieApiClient.js";
15 | import { ToolHandler } from "./handlers/ToolHandler.js";
16 | import { ResourceHandler } from "./handlers/ResourceHandler.js";
17 | import { PromptHandler } from "./handlers/PromptHandler.js";
18 | import { ErrorHandler } from "./error/ErrorHandler.js";
19 | class LottieServer {
20 | private readonly server: Server;
21 | private readonly apiClient: LottieApiClient;
22 | private readonly toolHandler: ToolHandler;
23 | private readonly resourceHandler: ResourceHandler;
24 | private readonly promptHandler: PromptHandler;
25 |
26 | constructor() {
27 | // Load environment variables
28 | dotenv.config();
29 |
30 | // Initialize API client
31 | this.apiClient = new LottieApiClient();
32 |
33 | // Initialize handlers
34 | this.toolHandler = new ToolHandler(this.apiClient);
35 | this.resourceHandler = new ResourceHandler(this.apiClient);
36 | this.promptHandler = new PromptHandler();
37 |
38 | // Initialize server with configuration
39 | this.server = this.initializeServer();
40 |
41 | // Setup handlers and error handling
42 | this.setupHandlers();
43 | this.setupErrorHandling();
44 | }
45 |
46 | private initializeServer(): Server {
47 | return new Server(
48 | {
49 | name: "lottiefiles-server",
50 | version: "1.0.0",
51 | },
52 | {
53 | capabilities: {
54 | tools: {},
55 | resources: {
56 | list: true,
57 | read: true,
58 | subscribe: false,
59 | },
60 | prompts: {
61 | list: true,
62 | get: true,
63 | },
64 | },
65 | }
66 | );
67 | }
68 |
69 | private setupErrorHandling(): void {
70 | this.server.onerror = (error) => {
71 | console.error("[MCP Error]", error);
72 | };
73 | process.on("SIGINT", async () => {
74 | await this.server.close();
75 | process.exit(0);
76 | });
77 | }
78 |
79 | private setupHandlers(): void {
80 | // Tool handlers
81 | this.server.setRequestHandler(ListToolsRequestSchema, async (request) => {
82 | return await this.toolHandler.listTools(request);
83 | });
84 |
85 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
86 | return await this.toolHandler.callTool(request);
87 | });
88 |
89 | // Resource handlers
90 | this.server.setRequestHandler(
91 | ListResourcesRequestSchema,
92 | async (request) => {
93 | return await this.resourceHandler.listResources(request);
94 | }
95 | );
96 |
97 | this.server.setRequestHandler(
98 | ReadResourceRequestSchema,
99 | async (request) => {
100 | return await this.resourceHandler.readResource(request);
101 | }
102 | );
103 |
104 | // Prompt handlers
105 | this.server.setRequestHandler(ListPromptsRequestSchema, async (request) => {
106 | return await this.promptHandler.listPrompts(request);
107 | });
108 |
109 | this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
110 | return await this.promptHandler.getPrompt(request);
111 | });
112 | }
113 |
114 | async run(): Promise<void> {
115 | const transport = new StdioServerTransport();
116 | await this.server.connect(transport);
117 | }
118 | }
119 |
120 | // Start the server
121 | const server = new LottieServer();
122 | server.run().catch((error) => {
123 | ErrorHandler.handleError(error);
124 | });
125 |
```
--------------------------------------------------------------------------------
/src/handlers/ToolHandler.ts:
--------------------------------------------------------------------------------
```typescript
1 | import {
2 | CallToolRequest,
3 | ListToolsRequest,
4 | } from "@modelcontextprotocol/sdk/types.js";
5 | import { LottieApiClient } from "../api/LottieApiClient.js";
6 | export class ToolHandler {
7 | constructor(private apiClient: LottieApiClient) {
8 | this.apiClient = apiClient;
9 | }
10 |
11 | async listTools(request: ListToolsRequest) {
12 | return {
13 | tools: [
14 | {
15 | name: "search_animations",
16 | description:
17 | "Search for Lottie animations by keywords, tags, and other criteria. Supports pagination.",
18 | inputSchema: {
19 | type: "object",
20 | properties: {
21 | query: {
22 | type: "string",
23 | description:
24 | "Search keywords that match animation names, descriptions, tags, etc.",
25 | },
26 | page: {
27 | type: "integer",
28 | description: "Page number, starting from 1",
29 | minimum: 1,
30 | default: 1,
31 | },
32 | limit: {
33 | type: "integer",
34 | description: "Number of items per page",
35 | minimum: 1,
36 | maximum: 100,
37 | default: 20,
38 | },
39 | },
40 | },
41 | },
42 | {
43 | name: "get_animation_details",
44 | description:
45 | "Get detailed information about a specific Lottie animation, including animation data, preview images, and tags.",
46 | inputSchema: {
47 | type: "object",
48 | properties: {
49 | id: {
50 | type: "string",
51 | description: "Unique identifier of the animation",
52 | },
53 | },
54 | required: ["id"],
55 | },
56 | },
57 | {
58 | name: "get_popular_animations",
59 | description: "Get a list of currently popular Lottie animations.",
60 | inputSchema: {
61 | type: "object",
62 | properties: {
63 | page: {
64 | type: "integer",
65 | description: "Page number, starting from 1",
66 | minimum: 1,
67 | default: 1,
68 | },
69 | limit: {
70 | type: "integer",
71 | description: "Number of items per page",
72 | minimum: 1,
73 | maximum: 100,
74 | default: 20,
75 | },
76 | },
77 | },
78 | },
79 | ],
80 | };
81 | }
82 |
83 | async callTool(request: CallToolRequest) {
84 | const { name, arguments: args } = request.params;
85 |
86 | switch (name) {
87 | case "search_animations":
88 | const list = await this.apiClient.searchAnimations(
89 | args?.query as string,
90 | args?.page as number,
91 | args?.limit as number
92 | );
93 |
94 | return {
95 | content: [
96 | {
97 | type: "text",
98 | text: JSON.stringify(
99 | {
100 | count: list.length,
101 | animations: list,
102 | },
103 | null,
104 | 2
105 | ),
106 | },
107 | ],
108 | };
109 |
110 | case "get_animation_details":
111 | const details = await this.apiClient.getAnimationById(
112 | args?.id as string
113 | );
114 |
115 | return {
116 | content: [
117 | {
118 | type: "text",
119 | text: JSON.stringify(details, null, 2),
120 | },
121 | ],
122 | };
123 |
124 | case "get_popular_animations":
125 | const popular = await this.apiClient.getPopularAnimations(
126 | args?.page as number,
127 | args?.limit as number
128 | );
129 | return {
130 | content: [
131 | {
132 | type: "text",
133 | text: JSON.stringify(
134 | {
135 | count: popular.length,
136 | popular: popular,
137 | },
138 | null,
139 | 2
140 | ),
141 | },
142 | ],
143 | };
144 |
145 | default:
146 | throw new Error(`Unknown tool: ${name}`);
147 | }
148 | }
149 | }
150 |
```