# Directory Structure
```
├── .editorconfig
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode
│ └── settings.json
├── .windsurfrules
├── Dockerfile
├── package.json
├── pnpm-lock.yaml
├── README.md
├── smithery.yaml
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
```
1 | build
2 | node_modules
3 | .git
4 | .DS_Store
5 |
```
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
```
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | indent_style = space
9 | indent_size = 2
10 |
11 | [*.{js,ts,json}]
12 | indent_style = space
13 | indent_size = 2
14 |
```
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
```
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "tabWidth": 2,
5 | "trailingComma": "es5",
6 | "printWidth": 100,
7 | "bracketSpacing": true,
8 | "arrowParens": "avoid",
9 | "plugins": ["prettier-plugin-organize-imports"]
10 | }
11 |
```
--------------------------------------------------------------------------------
/.windsurfrules:
--------------------------------------------------------------------------------
```
1 | This project is an MCP (Model Context Protocol) server that communicates with the OP.GG Esports API. The server provides a tool to fetch upcoming League of Legends match schedules from OP.GG Esports and returns formatted match information including match name, league, status, score, and scheduled time.
2 |
3 | When analyzing this project, focus on TypeScript patterns, error handling approaches, and GraphQL queries. The project uses node-fetch for API requests and follows a Promise-based async/await pattern throughout the codebase.
4 |
5 | ## Code Conventions for TypeScript
6 | - Prefer interfaces over type aliases for object types
7 | - Use readonly modifier for immutable properties
8 | - Use const assertions for literal values
9 | - Use mapped types and utility types when appropriate
10 | - Avoid any, use unknown when type cannot be determined
11 | - Organize imports alphabetically and by groups (built-in, external, internal)
12 | - Use Arrow functions for callbacks
13 | - Prefer template literals over string concatenation
14 | - Use optional chaining and nullish coalescing where appropriate
15 | - Avoid type assertions when possible
16 | - Prefer function declarations over function expressions
17 | - Use enums for values that represent a specific set of choices
18 |
19 | ## Documentation Guidelines
20 | - Add descriptive headers to all code files with purpose and author information
21 | - Each function should have a detailed comment block explaining its purpose, parameters, and return values
22 | - Use JSDoc style comments for TypeScript code documentation
23 | - Include examples in comments for complex logic or non-obvious implementations
24 | - Document all public interfaces and exported functions thoroughly
25 | - When writing complex algorithms, include comments explaining the approach
26 | - Add section headers as comments to organize large files (e.g. "// === API Handlers ===")
27 | - Use inline comments to explain "why" rather than "what" for non-obvious code
28 | - Document edge cases and error handling strategies in comments
29 | - Keep comments up-to-date when changing functionality
30 | - Include versioning information in file headers when making significant changes
31 | - Add TODO comments with ticket numbers for incomplete implementations
32 | - Add context and background information for workarounds or unusual approaches
33 | - Maintain a consistent commenting style throughout the codebase
34 | - Use descriptive variable and function names to reduce the need for obvious comments
35 |
36 | ## Code Structure and Refactoring Guidelines
37 | - Refactor files that exceed 200 lines of code into multiple files or classes
38 | - Each class should have a single responsibility (follow the Single Responsibility Principle)
39 | - Extract reusable logic into utility functions or service classes
40 | - Group related functionality into modules or namespaces
41 | - Use dependency injection for better testability and loose coupling
42 | - Keep public APIs minimal - expose only what is necessary
43 | - Consider breaking large functions (>50 lines) into smaller, more focused functions
44 | - Prefer composition over inheritance when designing class relationships
45 | - Use feature-based directory structure for complex applications
46 | - Keep nesting to a minimum (maximum 3-4 levels of nesting)
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # OP.GG Esports MCP Server
2 |
3 | [](https://smithery.ai/server/@opgginc/esports-mcp)
4 |
5 | The OP.GG Esports MCP Server is a Model Context Protocol implementation that seamlessly connects OP.GG Esports data with AI agents and platforms. This server enables AI agents to retrieve upcoming League of Legends match schedules and information via function calling.
6 |
7 | ## Overview
8 |
9 | This MCP server provides AI agents with access to OP.GG Esports data through a standardized interface. Built on TypeScript and Node.js, it connects directly to the OP.GG Esports GraphQL API and formats the data in a way that's easily consumable by AI models and agent frameworks.
10 |
11 | ## Features
12 |
13 | The OP.GG Esports MCP Server currently supports the following tools:
14 |
15 | - **get-lol-matches**: Fetch and format upcoming League of Legends match schedules from OP.GG Esports
16 | - Returns match name, league, status, score, scheduled time, and a direct link to the match
17 | - Formats the data in a clean, structured format for AI consumption
18 |
19 | ## Installation
20 |
21 | ### Installing via Smithery
22 |
23 | To install OP.GG Esports MCP for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@opgginc/esports-mcp):
24 |
25 | ```bash
26 | npx -y @smithery/cli install @opgginc/esports-mcp --client claude
27 | ```
28 |
29 | ### Using npm/pnpm
30 |
31 | ```bash
32 | # Install dependencies
33 | pnpm install
34 |
35 | # Build the project
36 | pnpm build
37 | ```
38 |
39 | ### Running the server
40 |
41 | #### Using pnpm
42 |
43 | ```bash
44 | # Start the MCP server on stdio
45 | pnpm start
46 | ```
47 |
48 | #### Using Node.js directly
49 |
50 | ```bash
51 | # Start using Node.js
52 | node dist/index.js
53 | ```
54 |
55 | #### Using npx
56 |
57 | ```bash
58 | # Run directly with npx
59 | npx -y @opgg/esports-mcp
60 | ```
61 |
62 | ### Adding to MCP configuration
63 |
64 | To add this server to your MCP configuration (e.g., Windsurf's mcp_config.json), add the following entry:
65 |
66 | ```json
67 | {
68 | "mcpServers": {
69 | "opgg-esports": {
70 | "command": "node",
71 | "args": ["/path/to/esports-mcp/dist/index.js"]
72 | }
73 | }
74 | }
75 | ```
76 |
77 | Alternatively, you can use the npm package if published:
78 |
79 | ```json
80 | {
81 | "mcpServers": {
82 | "opgg-esports": {
83 | "command": "npx",
84 | "args": ["-y", "@opgg/esports-mcp"]
85 | }
86 | }
87 | }
88 | ```
89 |
90 | ## Usage
91 |
92 | The OP.GG Esports MCP Server can be used with any MCP-compatible client. Here are some examples:
93 |
94 | ### Listing available tools
95 |
96 | ```json
97 | { "type": "list_tools" }
98 | ```
99 |
100 | Response:
101 | ```json
102 | {
103 | "tools": [
104 | {
105 | "name": "get-lol-matches",
106 | "description": "Get upcoming LoL match schedules from OP.GG Esports"
107 | }
108 | ]
109 | }
110 | ```
111 |
112 | ### Fetching upcoming match schedules
113 |
114 | ```json
115 | {
116 | "type": "tool_call",
117 | "tool_call": {
118 | "name": "get-lol-matches"
119 | }
120 | }
121 | ```
122 |
123 | Response:
124 | ```json
125 | {
126 | "content": [
127 | {
128 | "type": "text",
129 | "text": "Upcoming match schedules:\n\nMatch: Team A vs Team B\nLeague: LCK\nStatus: SCHEDULED\nScore: 0 - 0\nScheduled at: 4/6/2025, 7:00:00 PM\nDetails: https://esports.op.gg/matches/12345\n---\n..."
130 | }
131 | ]
132 | }
133 | ```
134 |
135 | ## License
136 |
137 | This project is licensed under the MIT License - see the LICENSE file for details.
138 |
139 | ## Related Links
140 |
141 | - [Model Context Protocol](https://modelcontextprotocol.com)
142 | - [OP.GG Esports](https://esports.op.gg)
143 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "esModuleInterop": true,
7 | "strict": true,
8 | "outDir": "./build",
9 | "rootDir": "./src",
10 | "resolveJsonModule": true
11 | },
12 | "include": ["src/**/*"],
13 | "exclude": ["node_modules", "build"]
14 | }
15 |
```
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "editor.formatOnSave": true,
3 | "editor.defaultFormatter": "esbenp.prettier-vscode",
4 | "[typescript]": {
5 | "editor.defaultFormatter": "esbenp.prettier-vscode"
6 | },
7 | "[javascript]": {
8 | "editor.defaultFormatter": "esbenp.prettier-vscode"
9 | },
10 | "[json]": {
11 | "editor.defaultFormatter": "esbenp.prettier-vscode"
12 | },
13 | "editor.codeActionsOnSave": {
14 | "source.organizeImports": "explicit"
15 | }
16 | }
17 |
```
--------------------------------------------------------------------------------
/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 | {}
8 | commandFunction:
9 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
10 | |-
11 | (config) => ({ command: 'node', args: ['build/index.js'] })
12 | exampleConfig: {}
13 |
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
2 | FROM node:lts-alpine
3 |
4 | # Install pnpm globally
5 | RUN npm install -g pnpm
6 |
7 | # Set working directory
8 | WORKDIR /app
9 |
10 | # Copy package files and lockfile
11 | COPY package.json pnpm-lock.yaml ./
12 |
13 | # Install dependencies
14 | RUN pnpm install --frozen-lockfile
15 |
16 | # Copy the rest of the source code
17 | COPY . .
18 |
19 | # Build the project
20 | RUN pnpm build
21 |
22 | # Expose any required port if necessary (none specified in this MCP)
23 |
24 | # Run the MCP server; entry point is build/index.js
25 | CMD [ "node", "build/index.js" ]
26 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "opgg-mcp-esports",
3 | "version": "1.0.0",
4 | "description": "OP.GG Esports MCP Server - LoL Match Schedule Search Tool",
5 | "main": "./build/index.js",
6 | "type": "module",
7 | "bin": {
8 | "opgg-mcp-esports": "./build/index.js"
9 | },
10 | "scripts": {
11 | "test": "npx @modelcontextprotocol/inspector node ./build/index.js",
12 | "build": "tsc && chmod 755 build/index.js"
13 | },
14 | "files": [
15 | "build"
16 | ],
17 | "keywords": [
18 | "opgg",
19 | "esports",
20 | "lol"
21 | ],
22 | "author": "",
23 | "license": "MIT",
24 | "dependencies": {
25 | "@modelcontextprotocol/sdk": "^1.8.0",
26 | "node-fetch": "^3.3.2"
27 | },
28 | "devDependencies": {
29 | "@types/node": "^22.14.0",
30 | "prettier": "^3.5.3",
31 | "prettier-plugin-organize-imports": "^4.1.0",
32 | "typescript": "^5.8.2"
33 | }
34 | }
35 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3 | import fetch from 'node-fetch';
4 |
5 | const GRAPHQL_ENDPOINT = 'https://esports.op.gg/matches/graphql';
6 |
7 | const UPCOMING_MATCHES_QUERY = `
8 | query MCPListUpcomingMatches {
9 | upcomingMatches {
10 | id
11 | name
12 | status
13 | awayScore
14 | homeScore
15 | scheduledAt
16 | numberOfGames
17 | tournament {
18 | serie {
19 | league {
20 | shortName
21 | }
22 | }
23 | }
24 | }
25 | }
26 | `;
27 |
28 | async function fetchUpcomingMatches() {
29 | try {
30 | const response = await fetch(GRAPHQL_ENDPOINT, {
31 | method: 'POST',
32 | headers: {
33 | 'Content-Type': 'application/json',
34 | 'User-Agent': 'MCP',
35 | },
36 | body: JSON.stringify({
37 | query: UPCOMING_MATCHES_QUERY,
38 | }),
39 | });
40 |
41 | if (!response.ok) {
42 | throw new Error(`API request failed: ${response.status} ${response.statusText}`);
43 | }
44 |
45 | const data = (await response.json()) as any;
46 |
47 | if (data.errors) {
48 | throw new Error(`GraphQL error: ${JSON.stringify(data.errors)}`);
49 | }
50 |
51 | return data.data.upcomingMatches;
52 | } catch (error) {
53 | console.error('Error calling OP.GG Esports API:', error);
54 | throw error;
55 | }
56 | }
57 |
58 | interface MatchInfo {
59 | id: number;
60 | name: string;
61 | status: string;
62 | homeScore: number;
63 | awayScore: number;
64 | scheduledAt: string | number | Date;
65 | league: string;
66 | numberOfGames?: number;
67 | }
68 |
69 | const server = new McpServer({
70 | name: 'opgg-mcp-esports',
71 | version: '1.0.0',
72 | });
73 |
74 | server.tool('get-lol-matches', 'Get upcoming LoL match schedules from OP.GG Esports', async () => {
75 | try {
76 | // Fetch match schedules
77 | const matches = await fetchUpcomingMatches();
78 |
79 | // Format results
80 | const formattedMatches = matches.map((match: any): MatchInfo => {
81 | const league = match.tournament?.serie?.league || {};
82 | return {
83 | id: match.id,
84 | name: match.name,
85 | status: match.status?.toUpperCase(),
86 | awayScore: match.awayScore,
87 | homeScore: match.homeScore,
88 | scheduledAt: match.scheduledAt,
89 | numberOfGames: match.numberOfGames,
90 | league: league.shortName || 'Unknown',
91 | };
92 | });
93 |
94 | return {
95 | content: [
96 | {
97 | type: 'text',
98 | text: `Upcoming match schedules:\n\n${formattedMatches
99 | .map(
100 | (match: MatchInfo) =>
101 | `Match: ${match.name}\nLeague: ${match.league}\nStatus: ${match.status}\nScore: ${match.homeScore} - ${match.awayScore}\nScheduled at: ${new Date(match.scheduledAt).toLocaleString()}\nDetails: https://esports.op.gg/matches/${match.id}\n---`
102 | )
103 | .join('\n')}`,
104 | },
105 | ],
106 | };
107 | } catch (error) {
108 | return {
109 | content: [
110 | {
111 | type: 'text',
112 | text: `Error fetching match schedules: ${error instanceof Error ? error.message : String(error)}`,
113 | },
114 | ],
115 | };
116 | }
117 | });
118 |
119 | async function main() {
120 | const transport = new StdioServerTransport();
121 | await server.connect(transport);
122 | }
123 |
124 | main().catch(error => {
125 | console.error('Fatal error in main():', error);
126 | process.exit(1);
127 | });
128 |
```