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

```
├── .gitignore
├── index.ts
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | build
 2 | node_modules
 3 | npm-debug.log
 4 | yarn-error.log
 5 | yarn-debug.log
 6 | yarn.lock
 7 | .DS_Store
 8 | .idea
 9 | .vscode
10 | coverage
11 | dist
12 | *.log
```

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

```markdown
  1 | # <img src="https://cdn.worldvectorlogo.com/logos/tiktok-icon-2.svg" height="32"> TikTok MCP
  2 | 
  3 | ![image (12)](https://github.com/user-attachments/assets/006f9983-b9dd-447c-87c6-ee27a414fd4c)
  4 | 
  5 | 
  6 | The TikTok MCP integrates TikTok access into Claude AI and other apps via TikNeuron. This TikTok MCP allows you to
  7 | - analyze TikTok videos to determine virality factors
  8 | - get content from TikTok videos
  9 | - chat with TikTok videos
 10 | 
 11 | ## Available Tools
 12 | 
 13 | ### tiktok_get_subtitle
 14 | 
 15 | **Description:**  
 16 | Get the subtitle (content) for a TikTok video url. This is used for getting the subtitle, content or context for a TikTok video. If no language code is provided, the tool will return the subtitle of automatic speech recognition.
 17 | 
 18 | **Input Parameters:**
 19 | - `tiktok_url` (required): TikTok video URL, e.g., https://www.tiktok.com/@username/video/1234567890 or https://vm.tiktok.com/1234567890
 20 | - `language_code` (optional): Language code for the subtitle, e.g., en for English, es for Spanish, fr for French, etc.
 21 | 
 22 | ### tiktok_get_post_details
 23 | 
 24 | **Description:**  
 25 | Get the details of a TikTok post. Returns the details of the video like:
 26 | - Description
 27 | - Video ID
 28 | - Creator username
 29 | - Hashtags
 30 | - Number of likes, shares, comments, views and bookmarks
 31 | - Date of creation
 32 | - Duration of the video
 33 | - Available subtitles with language and source information
 34 | 
 35 | **Input Parameters:**
 36 | - `tiktok_url` (required): TikTok video URL, e.g., https://www.tiktok.com/@username/video/1234567890 or https://vm.tiktok.com/1234567890, or just the video ID like 7409731702890827041
 37 | 
 38 | ### tiktok_search
 39 | 
 40 | **Description:**  
 41 | Search for TikTok videos based on a query. Returns a list of videos matching the search criteria with their details including description, video ID, creator, hashtags, engagement metrics, date of creation, duration and available subtitles, plus pagination metadata for continuing the search.
 42 | 
 43 | **Input Parameters:**
 44 | - `query` (required): Search query for TikTok videos, e.g., 'funny cats', 'dance', 'cooking tutorial'
 45 | - `cursor` (optional): Pagination cursor for getting more results
 46 | - `search_uid` (optional): Search session identifier for pagination
 47 | 
 48 | ## Requirements
 49 | 
 50 | For this TikTok MCP, you need
 51 | - NodeJS v18 or higher (https://nodejs.org/)
 52 | - Git (https://git-scm.com/)
 53 | - TikNeuron Account and MCP API Key (https://tikneuron.com/tools/tiktok-mcp)
 54 | 
 55 | ## Setup
 56 | 
 57 | 1. Clone the repository
 58 | ```
 59 | git clone https://github.com/Seym0n/tiktok-mcp.git
 60 | ```
 61 | 
 62 | 2. Install dependencies
 63 | ```
 64 | npm install
 65 | ```
 66 | 
 67 | 3. Build project
 68 | ```
 69 | npm run build
 70 | ```
 71 | 
 72 | This creates the file `build\index.js`
 73 | 
 74 | ## Using in Claude AI
 75 | 
 76 | Add the following entry to `mcpServers`:
 77 | 
 78 | ```
 79 | "tiktok-mcp": {
 80 |     "command": "node",
 81 |     "args": [
 82 |       "path\\build\\index.js"
 83 |     ],
 84 |     "env": {
 85 |       "TIKNEURON_MCP_API_KEY": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
 86 |     }
 87 | }
 88 | ```
 89 | 
 90 | and replace path with the `path` to TikTok MCP and `XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` with TIkNeuron API Key
 91 | 
 92 | so that `mcpServers` will look like this:
 93 | 
 94 | ```
 95 | {
 96 |   "mcpServers": {
 97 |     "tiktok-mcp": {
 98 |       "command": "node",
 99 |       "args": [
100 |         "path\\build\\index.js"
101 |       ],
102 |       "env": {
103 |         "TIKNEURON_MCP_API_KEY": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
104 |       }
105 |     }
106 |   }
107 | }
108 | ```
109 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2022",
 4 |       "module": "Node16",
 5 |       "moduleResolution": "Node16",
 6 |       "strict": true,
 7 |       "esModuleInterop": true,
 8 |       "skipLibCheck": true,
 9 |       "forceConsistentCasingInFileNames": true,
10 |       "resolveJsonModule": true,
11 |       "outDir": "./build",
12 |       "rootDir": "."
13 |     },
14 |     "exclude": ["node_modules"]
15 |   }
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "name": "tiktok-mcp",
 3 |     "version": "0.0.1",
 4 |     "description": "MCP server for TikTok",
 5 |     "license": "MIT",
 6 |     "author": "Simon",
 7 |     "homepage": "https://tikneuron.com",
 8 |     "type": "module",
 9 |     "bin": {
10 |       "tiktok-mcp-server": "build/index.js"
11 |     },
12 |     "files": [
13 |       "build"
14 |     ],
15 |     "scripts": {
16 |       "build": "tsc && shx chmod +x build/*.js",
17 |       "prepare": "npm run build",
18 |       "watch": "tsc --watch"
19 |     },
20 |     "dependencies": {
21 |       "@modelcontextprotocol/sdk": "1.10.1"
22 |     },
23 |     "devDependencies": {
24 |       "@types/node": "^22",
25 |       "shx": "^0.3.4",
26 |       "typescript": "^5.6.2"
27 |     }
28 |   }
```

--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5 | import {
  6 |     CallToolRequestSchema,
  7 |     ListToolsRequestSchema,
  8 |     Tool,
  9 | } from "@modelcontextprotocol/sdk/types.js";
 10 | 
 11 | 
 12 | const GET_SUBTITLE: Tool = {
 13 |     name: "tiktok_get_subtitle",
 14 |     description:
 15 |         "Get the subtitle (content) for a TikTok video url." +
 16 |         "This is used for getting the subtitle, content or context for a TikTok video." +
 17 |         "Supports TikTok video url (or video ID) as input and optionally language code from the tool post details" +
 18 |         "Returns the subtitle for the video in the requested language and format." +
 19 |         "If no language code is provided, the tool will return the subtitle of automatic speech recognition.",
 20 |     inputSchema: {
 21 |         type: "object",
 22 |         properties: {
 23 |             tiktok_url: {
 24 |                 type: "string",
 25 |                 description: "TikTok video URL, e.g., https://www.tiktok.com/@username/video/1234567890 or https://vm.tiktok.com/1234567890, or just the video ID like 7409731702890827041",
 26 |             },
 27 |             language_code: {
 28 |                 type: "string",
 29 |                 description: "Language code for the subtitle, e.g., en for English, es for Spanish, fr for French, etc.",
 30 |             },
 31 |         },
 32 |         required: ["tiktok_url"]
 33 |     }
 34 | };
 35 | 
 36 | const GET_POST_DETAILS: Tool = {
 37 |     name: "tiktok_get_post_details",
 38 |     description:
 39 |         "Get the details of a TikTok post." +
 40 |         "This is used for getting the details of a TikTok post." +
 41 |         "Supports TikTok video url (or video ID) as input." +
 42 |         "Returns the details of the video like" +
 43 |         " - Description" +
 44 |         " - Video ID" +
 45 |         " - Creator username" +
 46 |         " - Hashtags" + 
 47 |         " - Number of likes, shares, comments, views and bookmarks" +
 48 |         " - Date of creation" +
 49 |         " - Duration of the video" +
 50 |         " - Available subtitles with language and source information",
 51 |     inputSchema: {
 52 |         type: "object",
 53 |         properties: {
 54 |             tiktok_url: {
 55 |                 type: "string",
 56 |                 description: "TikTok video URL, e.g., https://www.tiktok.com/@username/video/1234567890 or https://vm.tiktok.com/1234567890, or just the video ID like 7409731702890827041",
 57 |             },
 58 |         },
 59 |         required: ["tiktok_url"],
 60 |     },
 61 | };
 62 | 
 63 | const SEARCH: Tool = {
 64 |     name: "tiktok_search",
 65 |     description:
 66 |         "Search for TikTok videos based on a query." +
 67 |         "This is used for searching TikTok videos by keywords, hashtags, or other search terms." +
 68 |         "Supports search query as input and optional cursor and search_uid for pagination." +
 69 |         "Returns a list of videos matching the search criteria with their details including" +
 70 |         " - Description, video ID, creator, hashtags, engagement metrics, date of creation, duration of the video and available subtitles with language and source information" +
 71 |         " - Pagination metadata for continuing the search",
 72 |     inputSchema: {
 73 |         type: "object",
 74 |         properties: {
 75 |             query: {
 76 |                 type: "string",
 77 |                 description: "Search query for TikTok videos, e.g., 'funny cats', 'dance', 'cooking tutorial'",
 78 |             },
 79 |             cursor: {
 80 |                 type: "string",
 81 |                 description: "Pagination cursor for getting more results (optional)",
 82 |             },
 83 |             search_uid: {
 84 |                 type: "string",
 85 |                 description: "Search session identifier for pagination (optional)",
 86 |             },
 87 |         },
 88 |         required: ["query"],
 89 |     },
 90 | };
 91 | 
 92 | // Server implementation
 93 | const server = new Server(
 94 |     {
 95 |         name: "tikneuron/tiktok-mcp",
 96 |         version: "0.1.0",
 97 |     },
 98 |     {
 99 |         capabilities: {
100 |             tools: {},
101 |         },
102 |     },
103 | );
104 | 
105 | // Check for API key
106 | const TIKNEURON_MCP_API_KEY = process.env.TIKNEURON_MCP_API_KEY!;
107 | if (!TIKNEURON_MCP_API_KEY) {
108 |     console.error("Error: TIKNEURON_MCP_API_KEY environment variable is required");
109 |     process.exit(1);
110 | }
111 | 
112 | 
113 | interface Subtitle {
114 |     success?: boolean;
115 |     subtitles?: Array<{
116 |         language?: string;
117 |         source?: string;
118 |     }>;
119 |     subtitle_content?: string;
120 | }
121 | 
122 | interface PostDetails {
123 |     success: boolean;
124 |     details: {
125 |         description: string;
126 |         video_id: string;
127 |         creator: string;
128 |         hashtags: string[];
129 |         likes: string;
130 |         shares: string;
131 |         comments: string;
132 |         views: string;
133 |         bookmarks: string;
134 |         created_at: string;
135 |         duration: number;
136 |         available_subtitles: Array<{
137 |             language?: string;
138 |             source?: string;
139 |         }>;
140 |     };
141 | }
142 | 
143 | interface SearchResult {
144 |     success: boolean;
145 |     videos: Array<{
146 |         description: string;
147 |         video_id: string;
148 |         creator: string;
149 |         hashtags: string[];
150 |         likes: string;
151 |         shares: string;
152 |         comments: string;
153 |         views: string;
154 |         bookmarks: string;
155 |         created_at: string;
156 |         duration: number;
157 |         available_subtitles: Array<{
158 |             language?: string;
159 |             source?: string;
160 |         }>;
161 |     }>;
162 |     metadata: {
163 |         cursor: string;
164 |         has_more: boolean;
165 |         search_uid: string;
166 |     };
167 | }
168 | 
169 | 
170 | 
171 | function isGetSubtitleArgs(args: unknown): args is { tiktok_url: string, language_code: string } {
172 |     return (
173 |         typeof args === "object" &&
174 |         args !== null &&
175 |         "tiktok_url" in args &&
176 |         typeof (args as { tiktok_url: string }).tiktok_url === "string"
177 |     );
178 | }
179 | 
180 | function isGetPostDetailsArgs(args: unknown): args is { tiktok_url: string } {
181 |     return (
182 |         typeof args === "object" &&
183 |         args !== null &&
184 |         "tiktok_url" in args &&
185 |         typeof (args as { tiktok_url: string }).tiktok_url === "string"
186 |     );
187 | }
188 | 
189 | function isSearchArgs(args: unknown): args is { query: string, cursor?: string, search_uid?: string } {
190 |     return (
191 |         typeof args === "object" &&
192 |         args !== null &&
193 |         "query" in args &&
194 |         typeof (args as { query: string }).query === "string" &&
195 |         ("cursor" in args ? typeof (args as { cursor?: string }).cursor === "string" : true) &&
196 |         ("search_uid" in args ? typeof (args as { search_uid?: string }).search_uid === "string" : true)
197 |     );
198 | }
199 | 
200 | async function performSearch(query: string, cursor?: string, search_uid?: string) {
201 |     const url = new URL('https://tikneuron.com/api/mcp/search');
202 |     url.searchParams.set('query', query);
203 |     
204 |     if (cursor) {
205 |         url.searchParams.set('cursor', cursor);
206 |     }
207 |     
208 |     if (search_uid) {
209 |         url.searchParams.set('search_uid', search_uid);
210 |     }
211 | 
212 |     const response = await fetch(url, {
213 |         headers: {
214 |             'Accept': 'application/json',
215 |             'Accept-Encoding': 'gzip',
216 |             'MCP-API-KEY': TIKNEURON_MCP_API_KEY,
217 |         }
218 |     });
219 | 
220 |     if (!response.ok) {
221 |         throw new Error(`TikNeuron API error: ${response.status} ${response.statusText}\n${await response.text()}`);
222 |     }
223 | 
224 |     const data = await response.json() as SearchResult;
225 | 
226 |     if (data.videos && data.videos.length > 0) {
227 |         const videosList = data.videos.map((video, index) => {
228 |             return `Video ${index + 1}:
229 | Description: ${video.description || 'N/A'}
230 | Video ID: ${video.video_id || 'N/A'}
231 | Creator: ${video.creator || 'N/A'}
232 | Hashtags: ${Array.isArray(video.hashtags) ? video.hashtags.join(', ') : 'N/A'}
233 | Likes: ${video.likes || '0'}
234 | Shares: ${video.shares || '0'}
235 | Comments: ${video.comments || '0'}
236 | Views: ${video.views || '0'}
237 | Bookmarks: ${video.bookmarks || '0'}
238 | Created at: ${video.created_at || 'N/A'}
239 | Duration: ${video.duration || 0} seconds
240 | Available subtitles: ${video.available_subtitles?.map(sub => `${sub.language || 'Unknown'} (${sub.source || 'Unknown source'})`).join(', ') || 'None'}`;
241 |         }).join('\n\n');
242 | 
243 |         const metadata = `\nSearch Metadata:
244 | Cursor: ${data.metadata?.cursor || 'N/A'}
245 | Has more results: ${data.metadata?.has_more ? 'Yes' : 'No'}
246 | Search UID: ${data.metadata?.search_uid || 'N/A'}`;
247 | 
248 |         return videosList + metadata;
249 |     } else {
250 |         return 'No videos found for the search query';
251 |     }
252 | }
253 | 
254 | async function performGetSubtitle(tiktok_url: string, language_code: string) {
255 |     const url = new URL('https://tikneuron.com/api/mcp/get-subtitles');
256 |     url.searchParams.set('tiktok_url', tiktok_url);
257 | 
258 |     if (language_code){
259 |         url.searchParams.set('language_code', language_code);
260 |     }
261 | 
262 |     const response = await fetch(url, {
263 |         headers: {
264 |             'Accept': 'application/json',
265 |             'Accept-Encoding': 'gzip',
266 |             'MCP-API-KEY': TIKNEURON_MCP_API_KEY,
267 |         }
268 |     });
269 | 
270 |     if (!response.ok) {
271 |         throw new Error(`TikNeuron API error: ${response.status} ${response.statusText}\n${await response.text()}`);
272 |     }
273 | 
274 |     const data = await response.json() as Subtitle;
275 | 
276 |     return data.subtitle_content || 'No subtitle available';
277 | }
278 | 
279 | async function performGetPostDetails(tiktok_url: string) {
280 |     const url = new URL('https://tikneuron.com/api/mcp/post-detail');
281 |     url.searchParams.set('tiktok_url', tiktok_url);
282 | 
283 |     const response = await fetch(url, {
284 |         headers: {
285 |             'Accept': 'application/json',
286 |             'Accept-Encoding': 'gzip',
287 |             'MCP-API-KEY': TIKNEURON_MCP_API_KEY,
288 |         }
289 |     });
290 | 
291 |     if (!response.ok) {
292 |         throw new Error(`TikNeuron API error: ${response.status} ${response.statusText}\n${await response.text()}`);
293 |     }
294 | 
295 |     const data = await response.json() as PostDetails;
296 | 
297 |     if (data.details) {
298 |         const details = data.details;
299 |         return `Description: ${details.description || 'N/A'}
300 |     Video ID: ${details.video_id || 'N/A'}
301 |     Creator: ${details.creator || 'N/A'}
302 |     Hashtags: ${Array.isArray(details.hashtags) ? details.hashtags.join(', ') : 'N/A'}
303 |     Likes: ${details.likes || '0'}
304 |     Shares: ${details.shares || '0'}
305 |     Comments: ${details.comments || '0'}
306 |     Views: ${details.views || '0'}
307 |     Bookmarks: ${details.bookmarks || '0'}
308 |     Created at: ${details.created_at || 'N/A'}
309 |     Duration: ${details.duration || 0} seconds
310 |     Available subtitles: ${details.available_subtitles?.map(sub => `${sub.language || 'Unknown'} (${sub.source || 'Unknown source'})`).join(', ') || 'None'}`;
311 |       } else {
312 |         return 'No details available';
313 |       }
314 | }
315 | 
316 | 
317 | // Tool handlers
318 | server.setRequestHandler(ListToolsRequestSchema, async () => ({
319 |     tools: [GET_SUBTITLE, GET_POST_DETAILS, SEARCH],
320 | }));
321 | 
322 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
323 |     try {
324 |         const { name, arguments: args } = request.params;
325 | 
326 |         if (!args) {
327 |             throw new Error("No arguments provided");
328 |         }
329 | 
330 |         switch (name) {
331 |             case "tiktok_get_subtitle": {
332 |                 if (!isGetSubtitleArgs(args)) {
333 |                     throw new Error("Invalid arguments for tiktok_get_subtitle");
334 |                 }
335 |                 const { tiktok_url, language_code } = args;
336 | 
337 |                 const results = await performGetSubtitle(tiktok_url, language_code);
338 |                 return {
339 |                     content: [{ type: "text", text: results }],
340 |                     isError: false,
341 |                 };
342 |             }
343 | 
344 |             case "tiktok_get_post_details": {
345 |                 if (!isGetPostDetailsArgs(args)) {
346 |                     throw new Error("Invalid arguments for tiktok_get_post_details");
347 |                 }
348 |                 const { tiktok_url } = args;
349 | 
350 |                 const results = await performGetPostDetails(tiktok_url);
351 |                 return {
352 |                     content: [{ type: "text", text: results }],
353 |                     isError: false,
354 |                 };
355 |             }
356 | 
357 |             case "tiktok_search": {
358 |                 if (!isSearchArgs(args)) {
359 |                     throw new Error("Invalid arguments for tiktok_search");
360 |                 }
361 |                 const { query, cursor, search_uid } = args;
362 | 
363 |                 const results = await performSearch(query, cursor, search_uid);
364 |                 return {
365 |                     content: [{ type: "text", text: results }],
366 |                     isError: false,
367 |                 };
368 |             }
369 | 
370 |             default:
371 |                 return {
372 |                     content: [{ type: "text", text: `Unknown tool: ${name}` }],
373 |                     isError: true,
374 |                 };
375 |         }
376 |     } catch (error) {
377 |         return {
378 |             content: [
379 |                 {
380 |                     type: "text",
381 |                     text: `Error: ${error instanceof Error ? error.message : String(error)}`,
382 |                 },
383 |             ],
384 |             isError: true,
385 |         };
386 |     }
387 | });
388 | 
389 | async function runServer() {
390 |     const transport = new StdioServerTransport();
391 |     await server.connect(transport);
392 |     console.error("TikTok MCP Server running on stdio");
393 | }
394 | 
395 | runServer().catch((error) => {
396 |     console.error("Fatal error running server:", error);
397 |     process.exit(1);
398 | });
```