# Directory Structure
```
├── .gitignore
├── .npmignore
├── package-lock.json
├── package.json
├── README.md
├── src
│ ├── constants.ts
│ ├── index.ts
│ ├── tools
│ │ ├── index.ts
│ │ └── match.ts
│ ├── types.ts
│ └── utils.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Dependencies
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
7 | # Build output
8 | dist/
9 | build/
10 |
11 | # IDE and editor files
12 | .idea/
13 | .vscode/
14 | *.swp
15 | *.swo
16 | .DS_Store
17 |
18 | # Environment variables
19 | .env
20 | .env.local
21 | .env.*.local
22 |
23 | # Logs
24 | logs/
25 | *.log
```
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
```
1 | # Source files
2 | src/
3 | *.ts
4 |
5 | # Development files
6 | .git/
7 | .gitignore
8 | .npmignore
9 | .editorconfig
10 | .eslintrc
11 | .prettierrc
12 | tsconfig.json
13 |
14 | # Test files
15 | __tests__/
16 | *.test.ts
17 | *.spec.ts
18 |
19 | # Documentation
20 | docs/
21 | *.md
22 | !README.md
23 |
24 | # Build files
25 | dist/*.map
26 |
27 | # IDE files
28 | .idea/
29 | .vscode/
30 | *.swp
31 | *.swo
32 |
33 | # OS files
34 | .DS_Store
35 | Thumbs.db
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # TFT MCP Server
2 |
3 | This is a Model Context Protocol (MCP) server for Team Fight Tactics (TFT) that provides access to TFT game data through various tools.
4 |
5 | ## Features
6 |
7 | - Get match history for a summoner
8 | - Get detailed information about specific TFT matches
9 |
10 | ## Prerequisites
11 |
12 | - Node.js (v14 or higher)
13 | - npm or yarn
14 | - Riot Games API Key (for accessing TFT data) - Get it from [Riot Games Developer Portal](https://developer.riotgames.com/)
15 | - Note: For development, you can use a temporary API key that expires in 24 hours
16 | - For production use, you'll need to apply for a permanent personal API key at [Riot's Application Portal](https://developer.riotgames.com/app-type)
17 | - Your Game Name, accessed from your Riot game console
18 | - Your Name Tagline, accessed from your Riot game console, which is usually followed/shown right after your Game Name. For example: `NA1`
19 |
20 | ## Usage
21 |
22 | 1. Configure the MCP server in your Claude Desktop config file:
23 |
24 | ### MacOS
25 |
26 | Location: `~/Library/Application Support/Claude/claude_desktop_config.json`
27 |
28 | ### Windows
29 |
30 | Location: `%APPDATA%/Claude/claude_desktop_config.json`
31 |
32 | Add the following configuration:
33 |
34 | ```json
35 | {
36 | "mcpServers": {
37 | "tft-mcp": {
38 | "command": "npx",
39 | "args": [
40 | "mcp-server-tft",
41 | "--apiKey",
42 | "<YOUR_RIOT_API_KEY>",
43 | "--gameName",
44 | "<YOUR_GAME_NAME>",
45 | "--tagLine",
46 | "<YOUR_TAG_LINE>"
47 | ]
48 | }
49 | }
50 | }
51 | ```
52 |
53 | 2. The server will run on stdio and provide the following tools:
54 |
55 | ### tft_match_history
56 |
57 | Get TFT match history for the current player.
58 |
59 | Parameters:
60 |
61 | - `count` (optional): Number of matches to retrieve. Defaults to 20
62 | - `start` (optional): Start index for pagination. Defaults to 0
63 |
64 | ### tft_match_details
65 |
66 | Get detailed information about a specific TFT match.
67 |
68 | Parameters:
69 |
70 | - `matchId` (required): The match ID to get details for
71 |
72 | ## Development
73 |
74 | The project is written in TypeScript and uses the Model Context Protocol SDK. To modify the code:
75 |
76 | 1. Make changes in the `src` directory
77 | 2. Run `npm run build` to compile
78 | 3. Run `npm start` with the required parameters to test changes
79 |
80 | ## License
81 |
82 | MIT
83 |
```
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
```typescript
1 | export const RIOT_ACCOUNT_API =
2 | "https://americas.api.riotgames.com/riot/account/v1";
3 | export const RIOT_API_BASE = "https://americas.api.riotgames.com";
4 |
```
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | export interface Arguments {
2 | apiKey: string;
3 | gameName: string;
4 | tagLine: string;
5 | [x: string]: unknown;
6 | }
7 |
8 | let _args: Arguments;
9 |
10 | export function setArgs(newArgs: Arguments) {
11 | _args = newArgs;
12 | }
13 |
14 | export function getArgs(): Arguments {
15 | return _args;
16 | }
17 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "esModuleInterop": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "outDir": "./dist",
11 | "rootDir": "./src"
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules", "dist"]
15 | }
16 |
```
--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2 |
3 | // Define tools using the Zod schemas
4 | export const TFT_TOOLS: Tool[] = [
5 | {
6 | name: "tft_match_history",
7 | description: "Get TFT match history for the current player",
8 | inputSchema: {
9 | type: "object",
10 | properties: {
11 | count: {
12 | type: "number",
13 | default: 20,
14 | description: "Number of matches to retrieve (default: 20)"
15 | },
16 | start: {
17 | type: "number",
18 | default: 0,
19 | description: "Start index (default: 0)"
20 | }
21 | }
22 | }
23 | },
24 | {
25 | name: "tft_match_details",
26 | description: "Get detailed information about a specific TFT match",
27 | inputSchema: {
28 | type: "object",
29 | properties: {
30 | matchId: {
31 | type: "string",
32 | description: "The match ID to get details for"
33 | }
34 | },
35 | required: ["matchId"]
36 | }
37 | }
38 | ];
39 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "mcp-server-tft",
3 | "version": "0.1.3",
4 | "description": "MCP Server for Team Fight Tactics (TFT)",
5 | "main": "dist/index.js",
6 | "type": "module",
7 | "bin": {
8 | "mcp-server-tft": "./dist/index.js"
9 | },
10 | "scripts": {
11 | "build": "tsc",
12 | "start": "node dist/index.js",
13 | "prepublishOnly": "npm run build"
14 | },
15 | "keywords": [
16 | "tft",
17 | "riot",
18 | "riot-games",
19 | "mcp",
20 | "model-context-protocol",
21 | "game-data"
22 | ],
23 | "author": "Your Name",
24 | "license": "MIT",
25 | "repository": {
26 | "type": "git",
27 | "url": "git+https://github.com/GeLi2001/tft-mcp-server.git"
28 | },
29 | "bugs": {
30 | "url": "https://github.com/GeLi2001/tft-mcp-server/issues"
31 | },
32 | "homepage": "https://github.com/GeLi2001/tft-mcp-server#readme",
33 | "dependencies": {
34 | "@modelcontextprotocol/sdk": "^1.8.0",
35 | "node-fetch": "^3.3.2",
36 | "cheerio": "^1.0.0-rc.12",
37 | "yargs": "^17.7.2",
38 | "zod": "^3.23.8"
39 | },
40 | "devDependencies": {
41 | "@types/node": "^20.11.19",
42 | "@types/yargs": "^17.0.32",
43 | "typescript": "^5.3.3"
44 | },
45 | "engines": {
46 | "node": ">=14.0.0"
47 | }
48 | }
49 |
```
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
```typescript
1 | import fetch from "node-fetch";
2 | import { RIOT_ACCOUNT_API } from "./constants.js";
3 | import { getArgs } from "./types.js";
4 |
5 | export let CURRENT_PUUID: string | null = null;
6 |
7 | export async function fetchWithErrorHandling(url: string, options: any = {}) {
8 | try {
9 | const headers = {
10 | "X-Riot-Token": getArgs().apiKey,
11 | ...options.headers
12 | };
13 |
14 | const response = await fetch(url, { ...options, headers });
15 | if (!response.ok) {
16 | throw new Error(`HTTP error! status: ${response.status}`);
17 | }
18 | return response;
19 | } catch (error) {
20 | throw new Error(
21 | `Failed to fetch: ${
22 | error instanceof Error ? error.message : String(error)
23 | }`
24 | );
25 | }
26 | }
27 |
28 | export async function initializePUUID() {
29 | try {
30 | const args = getArgs();
31 | const url = `${RIOT_ACCOUNT_API}/accounts/by-riot-id/${encodeURIComponent(
32 | args.gameName
33 | )}/${encodeURIComponent(args.tagLine)}`;
34 | const response = await fetchWithErrorHandling(url);
35 | const data = (await response.json()) as { puuid: string };
36 | CURRENT_PUUID = data.puuid;
37 | } catch (error) {
38 | console.error("Failed to initialize PUUID:", error);
39 | process.exit(1);
40 | }
41 | }
42 |
```
--------------------------------------------------------------------------------
/src/tools/match.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { RIOT_API_BASE } from "../constants.js";
2 | import { CURRENT_PUUID, fetchWithErrorHandling } from "../utils.js";
3 |
4 | // Tool handlers
5 | export async function handleTftMatchHistory(params: any) {
6 | const { count = 20, start = 0 } = params;
7 | const url = `${RIOT_API_BASE}/tft/match/v1/matches/by-puuid/${CURRENT_PUUID}/ids?start=${start}&count=${count}`;
8 |
9 | try {
10 | const response = await fetchWithErrorHandling(url);
11 | const matchIds = await response.json();
12 | return {
13 | content: [
14 | {
15 | type: "text",
16 | text: JSON.stringify({ matchIds }, null, 2)
17 | }
18 | ],
19 | isError: false
20 | };
21 | } catch (error) {
22 | return {
23 | content: [
24 | {
25 | type: "text",
26 | text: JSON.stringify(
27 | {
28 | error: error instanceof Error ? error.message : String(error)
29 | },
30 | null,
31 | 2
32 | )
33 | }
34 | ],
35 | isError: true
36 | };
37 | }
38 | }
39 |
40 | export async function handleTftMatchDetails(params: any) {
41 | const { matchId } = params;
42 | const url = `${RIOT_API_BASE}/tft/match/v1/matches/${matchId}`;
43 |
44 | try {
45 | const response = await fetchWithErrorHandling(url);
46 | const data = await response.json();
47 | return {
48 | content: [
49 | {
50 | type: "text",
51 | text: JSON.stringify(data, null, 2)
52 | }
53 | ],
54 | isError: false
55 | };
56 | } catch (error) {
57 | return {
58 | content: [
59 | {
60 | type: "text",
61 | text: JSON.stringify(
62 | {
63 | error: error instanceof Error ? error.message : String(error)
64 | },
65 | null,
66 | 2
67 | )
68 | }
69 | ],
70 | isError: true
71 | };
72 | }
73 | }
74 |
```
--------------------------------------------------------------------------------
/src/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 | ErrorCode,
8 | ListToolsRequestSchema,
9 | McpError
10 | } from "@modelcontextprotocol/sdk/types.js";
11 | import yargs from "yargs";
12 | import { hideBin } from "yargs/helpers";
13 | import { TFT_TOOLS } from "./tools/index.js";
14 | import { handleTftMatchDetails, handleTftMatchHistory } from "./tools/match.js";
15 | import { setArgs } from "./types.js";
16 | import { initializePUUID } from "./utils.js";
17 |
18 | // Parse command line arguments
19 | interface Arguments {
20 | apiKey: string;
21 | gameName: string;
22 | tagLine: string;
23 | [x: string]: unknown;
24 | }
25 |
26 | const getArgs = async (): Promise<Arguments> => {
27 | return yargs(hideBin(process.argv))
28 | .option("apiKey", {
29 | alias: "k",
30 | type: "string",
31 | description: "Riot API Key",
32 | demandOption: true
33 | })
34 | .option("gameName", {
35 | alias: "n",
36 | type: "string",
37 | description: "Summoner Name",
38 | demandOption: true
39 | })
40 | .option("tagLine", {
41 | alias: "t",
42 | type: "string",
43 | description: "Name Tagline",
44 | demandOption: true
45 | })
46 | .help()
47 | .parseAsync();
48 | };
49 |
50 | // Server setup
51 | const server = new Server(
52 | {
53 | name: "tft",
54 | version: "0.1.0"
55 | },
56 | {
57 | capabilities: {
58 | tools: {}
59 | }
60 | }
61 | );
62 |
63 | // Set up request handlers
64 | server.setRequestHandler(ListToolsRequestSchema, async () => ({
65 | tools: TFT_TOOLS
66 | }));
67 |
68 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
69 | try {
70 | switch (request.params.name) {
71 | case "tft_match_history": {
72 | return await handleTftMatchHistory(request.params.arguments);
73 | }
74 | case "tft_match_details": {
75 | return await handleTftMatchDetails(request.params.arguments);
76 | }
77 | default:
78 | throw new McpError(
79 | ErrorCode.MethodNotFound,
80 | `Unknown tool: ${request.params.name}`
81 | );
82 | }
83 | } catch (error) {
84 | return {
85 | content: [
86 | {
87 | type: "text",
88 | text: `Error: ${
89 | error instanceof Error ? error.message : String(error)
90 | }`
91 | }
92 | ],
93 | isError: true
94 | };
95 | }
96 | });
97 |
98 | // Modify runServer to initialize args and PUUID first
99 | async function runServer() {
100 | const parsedArgs = await getArgs();
101 | setArgs(parsedArgs);
102 | await initializePUUID();
103 | const transport = new StdioServerTransport();
104 | await server.connect(transport);
105 | console.error("TFT MCP Server running on stdio");
106 | }
107 |
108 | runServer().catch((error) => {
109 | console.error("Fatal error running server:", error);
110 | process.exit(1);
111 | });
112 |
```