# Directory Structure
```
├── .gitattributes
├── .github
│ ├── pull_request_template.md
│ └── workflows
│ ├── python.yml
│ └── typescript.yml
├── .gitignore
├── .npmrc
├── CONTRIBUTING-FOR-LLMS.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│ ├── server-curl
│ │ ├── LICENSE
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── server-llm-txt
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── schemas.ts
│ │ └── tsconfig.json
│ └── server-macos
│ ├── LICENSE
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
```
package-lock.json linguist-generated=true
```
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
```
registry="https://registry.npmjs.org/"
@modelcontextprotocol:registry="https://registry.npmjs.org/"
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
build/
gcp-oauth.keys.json
.*-server-credentials.json
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
.DS_Store
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
```
--------------------------------------------------------------------------------
/src/server-macos/README.md:
--------------------------------------------------------------------------------
```markdown
# @mcp-get-community/server-macos
A Model Context Protocol server that provides macOS-specific system information and operations.
## Features
- System information retrieval (CPU, Memory, Disk, Network)
- Native macOS notifications
## Installation
```bash
npx @michaellatman/mcp-get@latest install @mcp-get-community/server-macos
```
## Configuration
Add this to your configuration:
```json
{
"mcpServers": {
"@mcp-get-community/server-macos": {
"runtime": "node",
"command": "npx",
"args": [
"-y",
"@mcp-get-community/server-macos"
]
}
}
}
```
## Tools
### systemInfo
Retrieves system information from macOS.
Parameters:
- `category` (required): One of 'cpu', 'memory', 'disk', 'network', or 'all'
Example:
```json
{
"name": "systemInfo",
"arguments": {
"category": "cpu"
}
}
```
### sendNotification
Sends a native macOS notification.
Parameters:
- `title` (required): Title of the notification
- `message` (required): Content of the notification
- `sound` (optional): Whether to play a sound (default: true)
Example:
```json
{
"name": "sendNotification",
"arguments": {
"title": "Hello",
"message": "This is a test notification",
"sound": true
}
}
```
## Development
1. Install dependencies:
```bash
npm install
```
2. Build the project:
```bash
npm run build
```
3. Run in development mode:
```bash
npm run dev
```
## License
MIT
```
--------------------------------------------------------------------------------
/src/server-curl/README.md:
--------------------------------------------------------------------------------
```markdown
# @mcp-get-community/server-curl
An MCP server that allows LLMs to make HTTP requests to any URL using a curl-like interface.
## Features
- Make HTTP requests to any URL
- Support for all common HTTP methods (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
- Customizable headers and request body
- Configurable timeout
- Full response information including status, headers, and body
## Installation
```bash
npx @michaellatman/mcp-get@latest install @mcp-get-community/server-curl
```
## Usage
```json
{
"mcpServers": {
"@mcp-get-community/server-curl": {
"runtime": "node",
"command": "npx",
"args": [
"-y",
"@mcp-get-community/server-curl"
]
}
}
}
```
The server provides a single tool called `curl` that accepts the following parameters:
- `url` (required): The URL to make the request to
- `method` (optional): HTTP method to use (default: 'GET')
- `headers` (optional): Object containing HTTP headers
- `body` (optional): Request body for POST/PUT/PATCH requests
- `timeout` (optional): Request timeout in milliseconds (default: 30000, max: 300000)
### Example Request
```json
{
"url": "https://api.example.com/data",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer token123"
},
"body": "{\"key\": \"value\"}",
"timeout": 5000
}
```
### Example Response
```json
{
"status": 200,
"statusText": "OK",
"headers": {
"content-type": "application/json",
"server": "nginx"
},
"body": "{\"result\": \"success\"}"
}
```
## Development
```bash
# Install dependencies
npm install
# Build the project
npm run build
# Run in development mode
npm run dev
```
## License
MIT
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# MCP Get Community Servers
This repository contains a collection of community-maintained Model Context Protocol (MCP) servers. All servers are automatically listed on the [MCP Get registry](https://mcp-get.com) and can be viewed and installed via CLI:
```bash
npx @michaellatman/mcp-get@latest list
```
> **Note:** While we review all servers in this repository, they are maintained by their respective creators who are responsible for their functionality and maintenance.
## Available Servers
- **[LLM.txt Server](src/server-llm-txt)** - A server for searching and retrieving content from [LLM.txt](https://llmstxt.org/) files. Provides tools for listing available files, fetching content, and performing contextual searches.
- **[Curl Server](src/server-curl)** - A server that allows LLMs to make HTTP requests to any URL using a curl-like interface. Supports all common HTTP methods, custom headers, request body, and configurable timeouts.
- **[macOS Server](src/server-macos)** - A server that provides macOS-specific system information and operations.
## Installation
You can install any server using the MCP Get CLI:
```bash
npx @michaellatman/mcp-get@latest install <server-name>
```
For example:
```bash
npx @michaellatman/mcp-get@latest install @mcp-get-community/server-curl
```
## Development
To run in development mode with automatic recompilation:
```bash
npm install
npm run watch
```
## Contributing
We welcome contributions! Please feel free to submit a Pull Request.
## License
While this repository's structure and documentation are licensed under the MIT License, individual servers may have their own licenses. Please check each server's documentation in the [src](src) directory for its specific license terms.
## Support
If you find these servers useful, please consider starring the repository!
```
--------------------------------------------------------------------------------
/src/server-llm-txt/README.md:
--------------------------------------------------------------------------------
```markdown
# LLM.txt MCP Server
[](https://mcp-get.com/packages/%40mcp-get-community%2Fserver-llm-txt)
A Model Context Protocol (MCP) server that extracts and serves context from llm.txt files, enabling AI models to understand file structure, dependencies, and code relationships in development environments. This server provides comprehensive access to the [LLM.txt Directory](https://directory.llmstxt.cloud/), supporting file listing, content retrieval, and advanced multi-query search capabilities.
## Features
- Directory listing with local caching (24-hour cache)
- OS-specific cache locations:
- Windows: `%LOCALAPPDATA%\llm-txt-mcp`
- macOS: `~/Library/Caches/llm-txt-mcp`
- Linux: `~/.cache/llm-txt-mcp`
- Multi-query search with context
## Installation
### Recommended: Using MCP Get
The easiest way to install is using MCP Get, which will automatically configure the server in Claude Desktop:
```bash
npx @michaellatman/mcp-get@latest install @mcp-get-community/server-llm-txt
```
### Manual Configuration
Alternatively, you can manually configure the server in your Claude Desktop configuration by adding this to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"llm-txt": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-llm-txt"
]
}
}
}
```
## Tools
### 1. list_llm_txt
Lists all available LLM.txt files from the directory. Results are cached locally for 24 hours.
Example response:
```json
[
{
"id": 1,
"url": "https://docs.squared.ai/llms.txt",
"name": "AI Squared",
"description": "AI Squared provides a data and AI integration platform that helps make intelligent insights accessible to all."
}
]
```
### 2. get_llm_txt
Fetches content from an LLM.txt file by ID (obtained from list_llm_txt).
Parameters:
- `id`: The numeric ID of the LLM.txt file
Example response:
```json
{
"id": 1,
"url": "https://docs.squared.ai/llms.txt",
"name": "AI Squared",
"description": "AI Squared provides a data and AI integration platform that helps make intelligent insights accessible to all.",
"content": "# AI Squared\n\n## Docs\n\n- [Create Catalog](https://docs.squared.ai/api-reference/catalogs/create_catalog)\n- [Update Catalog](https://docs.squared.ai/api-reference/catalogs/update_catalog)\n..."
}
```
### 3. search_llm_txt
Search for multiple substrings within an LLM.txt file.
Parameters:
- `id`: The numeric ID of the LLM.txt file
- `queries`: Array of strings to search for (case-insensitive)
- `context_lines` (optional): Number of lines to show before and after matches (default: 2)
Example response:
```json
{
"id": 1,
"url": "https://docs.squared.ai/llms.txt",
"name": "AI Squared",
"matches": [
{
"lineNumber": 42,
"snippet": "- [PostgreSQL](https://docs.squared.ai/guides/data-integration/destinations/database/postgresql): PostgreSQL\n popularly known as Postgres, is a powerful, open-source object-relational database system that uses and extends the SQL language combined with many features that safely store and scale data workloads.\n- [null](https://docs.squared.ai/guides/data-integration/destinations/e-commerce/facebook-product-catalog)",
"matchedLine": "- [PostgreSQL](https://docs.squared.ai/guides/data-integration/destinations/database/postgresql): PostgreSQL\n popularly known as Postgres, is a powerful, open-source object-relational database system that uses and extends the SQL language combined with many features that safely store and scale data workloads.",
"matchedQueries": ["postgresql", "database"]
}
]
}
```
## FAQ
### Why do the tools use numeric IDs instead of string identifiers?
While the examples above show string IDs for clarity, the actual implementation uses numeric IDs. We found that when using string IDs (like domain names or slugs), language models were more likely to hallucinate plausible-looking but non-existent LLM.txt files. Using opaque numeric IDs encourages models to actually check the list of available files first rather than guessing at possible IDs.
## Development
To run in development mode with automatic recompilation:
```bash
npm install
npm run watch
```
## License
MIT
```
--------------------------------------------------------------------------------
/src/server-curl/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/src/server-macos/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/src/server-llm-txt/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["./**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@mcp-get-community/servers",
"private": true,
"version": "0.6.1",
"description": "MCP Get servers - A collection of community MCP servers",
"license": "MIT",
"author": "Michael Latman (https://michaellatman.com)",
"homepage": "https://github.com/mcp-get/community-servers",
"bugs": "https://github.com/mcp-get/community-servers/issues",
"type": "module",
"workspaces": [
"src/*"
],
"files": [],
"scripts": {
"build": "npm run build --workspaces",
"watch": "npm run watch --workspaces",
"publish-all": "npm publish --workspaces --access public",
"link-all": "npm link --workspaces"
},
"dependencies": {
"@mcp-get-community/server-llm-txt": "*"
}
}
```
--------------------------------------------------------------------------------
/src/server-macos/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@mcp-get-community/server-macos",
"version": "0.1.0",
"description": "MCP server for macOS system operations",
"main": "dist/index.js",
"type": "module",
"private": false,
"bin": {
"@mcp-get-community/server-macos": "./dist/index.js"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"publishConfig": {
"access": "public"
},
"license": "MIT",
"author": "Michael Latman <https://michaellatman.com>",
"homepage": "https://github.com/mcp-get-community/server-macos#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/mcp-get-community/server-macos.git"
},
"bugs": {
"url": "https://github.com/mcp-get-community/server-macos/issues"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.0.1",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.3"
},
"devDependencies": {
"@types/node": "^20.10.0",
"typescript": "^5.3.0",
"ts-node": "^10.9.0"
}
}
```
--------------------------------------------------------------------------------
/src/server-curl/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@mcp-get-community/server-curl",
"version": "0.1.0",
"description": "MCP server for making HTTP requests using a curl-like interface",
"main": "dist/index.js",
"type": "module",
"private": false,
"bin": {
"@mcp-get-community/server-curl": "./dist/index.js"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"publishConfig": {
"access": "public"
},
"license": "MIT",
"author": "Michael Latman <https://michaellatman.com>",
"homepage": "https://github.com/mcp-get-community/server-curl#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/mcp-get-community/server-curl.git"
},
"bugs": {
"url": "https://github.com/mcp-get-community/server-curl/issues"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.0.1",
"node-fetch": "^3.3.0",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.3"
},
"devDependencies": {
"@types/node": "^20.10.0",
"typescript": "^5.3.0",
"ts-node": "^10.9.0"
}
}
```
--------------------------------------------------------------------------------
/src/server-llm-txt/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@mcp-get-community/server-llm-txt",
"version": "0.6.2",
"description": "MCP server that extracts and serves context from llm.txt files, enabling AI models to understand file structure, dependencies, and code relationships in development environments",
"author": "Michael Latman (https://michaellatman.com)",
"license": "MIT",
"type": "module",
"bin": {
"mcp-server-llm-txt": "dist/index.js"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.0.1",
"@types/node-fetch": "^2.6.12",
"node-fetch": "^3.3.2",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.23.5",
"jsdom": "^22.1.0",
"@types/jsdom": "^21.1.6"
},
"devDependencies": {
"@types/node": "^20.10.4",
"shx": "^0.3.4",
"typescript": "^5.6.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mcp-get/community-servers.git"
},
"bugs": {
"url": "https://github.com/mcp-get/community-servers/issues"
},
"homepage": "https://github.com/mcp-get/community-servers#readme",
"publishConfig": {
"access": "public"
}
}
```
--------------------------------------------------------------------------------
/src/server-llm-txt/schemas.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from 'zod';
// Schema for items in the list view (without content)
export const LLMTxtListItemSchema = z.object({
id: z.number(),
url: z.string().url(),
name: z.string(),
description: z.string()
});
export type LLMTxtListItem = z.infer<typeof LLMTxtListItemSchema>;
// Schema for a full item with content
export const LLMTxtSchema = z.object({
id: z.number(),
url: z.string().url(),
name: z.string(),
description: z.string(),
content: z.string(),
hasNextPage: z.boolean().optional(),
currentPage: z.number()
});
export type LLMTxt = z.infer<typeof LLMTxtSchema>;
// List of items (without content)
export const LLMTxtListSchema = z.array(LLMTxtListItemSchema);
export type LLMTxtList = z.infer<typeof LLMTxtListSchema>;
export const GetLLMTxtOptionsSchema = z.object({
id: z.number().describe(
"The ID of the LLM.txt file to fetch. Must be obtained first using the list_llm_txt command."
),
page: z.number().optional().default(1).describe(
"Page number to fetch, starting from 1. Each page contains a fixed number of characters."
)
});
export type GetLLMTxtOptions = z.infer<typeof GetLLMTxtOptionsSchema>;
export const ListLLMTxtOptionsSchema = z.object({
// Empty object since we're removing pagination
});
export type ListLLMTxtOptions = z.infer<typeof ListLLMTxtOptionsSchema>;
```
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
```markdown
<!-- Provide a brief description of your changes -->
## Description
## Server Details
<!-- If modifying an existing server or adding a new one -->
- Server: <!-- e.g., server-llm-txt -->
- Package Name: <!-- e.g., @mcp-get/server-llm-txt -->
- Changes to: <!-- e.g., tools, resources, prompts -->
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
## How Has This Been Tested?
<!-- Have you tested this with an LLM client? Which scenarios were tested? -->
## Breaking Changes
<!-- Will users need to update their MCP client configurations? -->
## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation update
- [ ] New server
## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes that apply. -->
- [ ] I have read the [MCP Protocol Documentation](https://modelcontextprotocol.io)
- [ ] My changes follow MCP security best practices
- [ ] I have updated the server's README accordingly
- [ ] I have tested this with an LLM client
- [ ] My code follows the repository's style guidelines
- [ ] I have added appropriate error handling
- [ ] I have documented all environment variables and configuration options
- [ ] I have added the server to the root README.md (if adding a new server)
- [ ] I have set up automatic publishing via MCP Get
## Additional context
<!-- Add any other context, implementation notes, or design decisions -->
```
--------------------------------------------------------------------------------
/.github/workflows/python.yml:
--------------------------------------------------------------------------------
```yaml
name: Python
on:
push:
branches:
- main
pull_request:
jobs:
detect-packages:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.find-packages.outputs.packages }}
has_packages: ${{ steps.find-packages.outputs.has_packages }}
steps:
- uses: actions/checkout@v4
- name: Find Python packages
id: find-packages
working-directory: src
run: |
PACKAGES=$(find . -name pyproject.toml -exec dirname {} \; | sed 's/^\.\///' | jq -R -s -c 'split("\n")[:-1]')
echo "packages=$PACKAGES" >> $GITHUB_OUTPUT
if [ "$PACKAGES" = "[]" ]; then
echo "has_packages=false" >> $GITHUB_OUTPUT
else
echo "has_packages=true" >> $GITHUB_OUTPUT
fi
build:
needs: [detect-packages]
if: needs.detect-packages.outputs.has_packages == 'true'
strategy:
matrix:
package: ${{ fromJson(needs.detect-packages.outputs.packages) }}
name: Build ${{ matrix.package }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: "src/${{ matrix.package }}/.python-version"
- name: Install dependencies
working-directory: src/${{ matrix.package }}
run: uv sync --frozen --all-extras --dev
- name: Run pyright
working-directory: src/${{ matrix.package }}
run: uv run --frozen pyright
- name: Build package
working-directory: src/${{ matrix.package }}
run: uv build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.package }}
path: src/${{ matrix.package }}/dist/
publish:
runs-on: ubuntu-latest
needs: [build, detect-packages]
if: github.ref == 'refs/heads/main' && needs.detect-packages.outputs.has_packages == 'true'
strategy:
matrix:
package: ${{ fromJson(needs.detect-packages.outputs.packages) }}
name: Publish ${{ matrix.package }}
environment: release
permissions:
id-token: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist-${{ matrix.package }}
path: dist/
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
```
--------------------------------------------------------------------------------
/.github/workflows/typescript.yml:
--------------------------------------------------------------------------------
```yaml
name: TypeScript
on:
push:
branches:
- main
pull_request:
jobs:
detect-packages:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.find-packages.outputs.packages }}
steps:
- uses: actions/checkout@v4
- name: Find JS packages
id: find-packages
working-directory: src
run: |
PACKAGES=$(find . -name package.json -not -path "*/node_modules/*" -exec dirname {} \; | sed 's/^\.\///' | jq -R -s -c 'split("\n")[:-1]')
echo "packages=$PACKAGES" >> $GITHUB_OUTPUT
build:
needs: [detect-packages]
strategy:
matrix:
package: ${{ fromJson(needs.detect-packages.outputs.packages) }}
name: Build ${{ matrix.package }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm
- name: Install dependencies
working-directory: src/${{ matrix.package }}
run: npm ci
- name: Build package
working-directory: src/${{ matrix.package }}
run: npm run build
publish:
runs-on: ubuntu-latest
needs: [build, detect-packages]
if: github.ref == 'refs/heads/main'
environment: release
strategy:
matrix:
package: ${{ fromJson(needs.detect-packages.outputs.packages) }}
fail-fast: false
continue-on-error: true
name: Publish ${{ matrix.package }}
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
working-directory: src/${{ matrix.package }}
run: npm ci
- name: Check if version exists
id: version_check
working-directory: src/${{ matrix.package }}
run: |
PKG_NAME=$(node -p "require('./package.json').name")
PKG_VERSION=$(node -p "require('./package.json').version")
if npm view "$PKG_NAME@$PKG_VERSION" version &> /dev/null; then
echo "Version $PKG_VERSION already exists for $PKG_NAME, skipping publish"
echo "should_publish=false" >> $GITHUB_OUTPUT
else
echo "Version $PKG_VERSION is new for $PKG_NAME, will publish"
echo "should_publish=true" >> $GITHUB_OUTPUT
fi
- name: Publish package
if: steps.version_check.outputs.should_publish == 'true'
working-directory: src/${{ matrix.package }}
run: npm publish # --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
--------------------------------------------------------------------------------
/src/server-curl/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
type CallToolRequest
} from "@modelcontextprotocol/sdk/types.js";
import fetch from "node-fetch";
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
// Define schema for curl request options
const CurlOptionsSchema = z.object({
url: z.string().url().describe("The URL to make the request to"),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']).default('GET')
.describe("HTTP method to use"),
headers: z.record(z.string()).optional()
.describe("HTTP headers to include in the request"),
body: z.string().optional()
.describe("Request body (for POST, PUT, PATCH requests)"),
timeout: z.number().min(0).max(300000).optional().default(30000)
.describe("Request timeout in milliseconds (max 300000ms/5min)")
});
const tools = {
curl: {
description: "Make an HTTP request to any URL with customizable method, headers, and body.",
inputSchema: zodToJsonSchema(CurlOptionsSchema)
}
};
const server = new Server({
name: "@mcp-get-community/server-curl",
version: "0.1.0",
author: "MCP Community"
}, {
capabilities: {
tools
}
});
async function makeCurlRequest(options: z.infer<typeof CurlOptionsSchema>) {
const { url, method, headers, body, timeout } = options;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
method,
headers: {
...headers,
'User-Agent': '@mcp-get-community/server-curl'
},
body: body,
signal: controller.signal
});
const responseBody = await response.text();
const responseHeaders = Object.fromEntries(response.headers.entries());
return {
status: response.status,
statusText: response.statusText,
headers: responseHeaders,
body: responseBody
};
} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new Error(`Request timed out after ${timeout}ms`);
}
throw new Error(`Curl request failed: ${error.message}`);
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
try {
if (!request.params.arguments) {
throw new Error("Arguments are required");
}
switch (request.params.name) {
case "curl": {
const args = CurlOptionsSchema.parse(request.params.arguments);
const result = await makeCurlRequest(args);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error: unknown) {
if (error instanceof z.ZodError) {
const issues = error.issues.map((issue: z.ZodIssue) => `${issue.path.join('.')}: ${issue.message}`).join(', ');
throw new Error(`Invalid arguments: ${issues}`);
}
throw error;
}
});
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: Object.entries(tools).map(([name, tool]) => ({
name,
description: tool.description,
inputSchema: tool.inputSchema
}))
};
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Curl MCP Server running on stdio");
}
runServer().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/src/server-macos/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
type CallToolRequest
} from "@modelcontextprotocol/sdk/types.js";
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { promisify } from 'util';
import { exec as execCallback } from 'child_process';
const exec = promisify(execCallback);
// Define schemas for macOS tools
const SystemInfoSchema = z.object({
category: z.enum(['cpu', 'memory', 'disk', 'network', 'all'])
.describe('Category of system information to retrieve')
});
const NotificationSchema = z.object({
title: z.string().describe('Title of the notification'),
message: z.string().describe('Content of the notification'),
sound: z.boolean().optional().default(true).describe('Whether to play a sound')
});
interface SystemInfo {
model?: string;
cores?: number;
total?: string;
vmStat?: string;
diskInfo?: string;
networkInfo?: string;
}
const tools = {
systemInfo: {
description: "Retrieve system information from macOS using various system commands",
inputSchema: zodToJsonSchema(SystemInfoSchema)
},
sendNotification: {
description: "Send a native macOS notification",
inputSchema: zodToJsonSchema(NotificationSchema)
}
};
const server = new Server({
name: "@mcp-get-community/server-macos",
version: "0.1.0",
author: "Michael Latman <https://michaellatman.com>"
}, {
capabilities: {
tools
}
});
async function getSystemInfo(category: string): Promise<SystemInfo | { cpu: SystemInfo; memory: SystemInfo; disk: SystemInfo; network: SystemInfo }> {
switch (category) {
case 'cpu': {
const { stdout } = await exec('sysctl -n machdep.cpu.brand_string && sysctl -n hw.ncpu');
const [model, cores] = stdout.split('\n');
return { model, cores: parseInt(cores) };
}
case 'memory': {
const { stdout } = await exec('sysctl -n hw.memsize && vm_stat');
const [totalBytes, vmStat] = stdout.split('\n', 2);
const total = parseInt(totalBytes) / (1024 * 1024 * 1024);
return { total: `${total.toFixed(2)} GB`, vmStat };
}
case 'disk': {
const { stdout } = await exec('df -h /');
return { diskInfo: stdout };
}
case 'network': {
const { stdout } = await exec('ifconfig en0');
return { networkInfo: stdout };
}
case 'all': {
const [cpu, memory, disk, network] = await Promise.all([
getSystemInfo('cpu') as Promise<SystemInfo>,
getSystemInfo('memory') as Promise<SystemInfo>,
getSystemInfo('disk') as Promise<SystemInfo>,
getSystemInfo('network') as Promise<SystemInfo>
]);
return { cpu, memory, disk, network };
}
default:
throw new Error(`Unknown category: ${category}`);
}
}
async function sendNotification(options: z.infer<typeof NotificationSchema>) {
const { title, message, sound } = options;
const soundFlag = sound ? 'default' : 'none';
const script = `display notification "${message}" with title "${title}" sound name "${soundFlag}"`;
await exec(`osascript -e '${script}'`);
return { success: true, message: 'Notification sent' };
}
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
try {
if (!request.params.arguments) {
throw new Error("Arguments are required");
}
switch (request.params.name) {
case "systemInfo": {
const args = SystemInfoSchema.parse(request.params.arguments);
const result = await getSystemInfo(args.category);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
}
case "sendNotification": {
const args = NotificationSchema.parse(request.params.arguments);
const result = await sendNotification(args);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error: unknown) {
if (error instanceof z.ZodError) {
const issues = error.issues.map((issue: z.ZodIssue) => `${issue.path.join('.')}: ${issue.message}`).join(', ');
throw new Error(`Invalid arguments: ${issues}`);
}
throw error;
}
});
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: Object.entries(tools).map(([name, tool]) => ({
name,
description: tool.description,
inputSchema: tool.inputSchema
}))
};
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("macOS MCP Server running on stdio");
}
runServer().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
```
--------------------------------------------------------------------------------
/CONTRIBUTING-FOR-LLMS.md:
--------------------------------------------------------------------------------
```markdown
# Contributing Guide for LLMs
## Quick Checklist
- [ ] **Project Structure**
- [ ] Directory follows `src/server-{name}` pattern
- [ ] Contains `src/index.ts`, `package.json`, `tsconfig.json`, `README.md`, `LICENSE`
- [ ] **Package Configuration**
- [ ] Name follows `@mcp-get-community/server-{name}` format
- [ ] Required dependencies installed
- [ ] Author set to "Michael Latman <https://michaellatman.com>"
- [ ] MIT license specified
- [ ] Repository information included
- [ ] Publishing configuration:
- [ ] `"private": false` set
- [ ] `"bin"` field configured for npx execution
- [ ] `"files"` field specifies publishable files
- [ ] `"publishConfig"` set to public access
- [ ] `"prepublishOnly"` script added
- [ ] **TypeScript Setup**
- [ ] ES modules support configured
- [ ] Strict mode enabled
- [ ] No TypeScript errors
- [ ] **Server Implementation**
- [ ] Required SDK imports
- [ ] Zod schemas for tools
- [ ] Tool descriptions and schemas
- [ ] Proper error handling
- [ ] Request handlers implemented
- [ ] **Documentation**
- [ ] Clear server description
- [ ] Features list
- [ ] Installation instructions
- [ ] Configuration example
- [ ] Tool documentation
- [ ] Example requests/responses
- [ ] Development instructions
- [ ] **Final Steps**
- [ ] MIT License file created
- [ ] Root README.md updated
- [ ] All tests passing
- [ ] Code properly formatted
This guide walks through the steps for creating a new MCP server in this repository. Follow these steps in order.
## Step 0: Check Existing Servers
Before creating a new server, check the existing servers in the `src` directory for reference:
- [server-llm-txt](src/server-llm-txt) - Example of a server that handles file fetching and searching
- [server-curl](src/server-curl) - Example of a server that makes HTTP requests
These servers demonstrate:
- Proper project structure
- TypeScript configuration
- Error handling patterns
- Documentation standards
- Tool schema definitions
- Request/response formatting
Use them as templates and adapt their patterns to your needs.
## Step 1: Project Structure
Create a new directory in the `src` folder with your server name, following the pattern `server-{name}`. Your directory should have this structure:
```
src/server-{name}/
├── src/
│ └── index.ts # Main server implementation
├── package.json # Dependencies and metadata
├── tsconfig.json # TypeScript configuration
├── README.md # Documentation
└── LICENSE # MIT License
```
## Step 2: Package Configuration
Create a `package.json` with:
- Name format: `@mcp-get-community/server-{name}`
- Required dependencies: `@modelcontextprotocol/sdk`, `zod`, `zod-to-json-schema`
- TypeScript development dependencies
- MIT license
- Author and repository information
Example:
```json
{
"name": "@mcp-get-community/server-{name}",
"version": "0.1.0",
"description": "MCP server for ...",
"main": "dist/index.js",
"type": "module",
"private": false,
"bin": {
"@mcp-get-community/server-{name}": "./dist/index.js"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"publishConfig": {
"access": "public"
},
"license": "MIT",
"author": "Michael Latman <https://michaellatman.com>",
"homepage": "https://github.com/mcp-get-community/server-{name}#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/mcp-get-community/server-{name}.git"
},
"bugs": {
"url": "https://github.com/mcp-get-community/server-{name}/issues"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.0.1",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.3"
},
"devDependencies": {
"@types/node": "^20.10.0",
"typescript": "^5.3.0",
"ts-node": "^10.9.0"
}
}
```
The package configuration includes several important fields for npm publishing:
- `private`: Must be set to `false` to allow publishing
- `bin`: Makes the server executable via npx, pointing to the compiled JavaScript file
- `files`: Specifies which files should be included in the published package
- `publishConfig`: Ensures the package is published with public access
- `prepublishOnly`: Automatically builds TypeScript before publishing
## Step 3: TypeScript Configuration
Create a `tsconfig.json` with ES modules support:
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
## Step 4: Server Implementation
Create `src/index.ts` with:
1. Required imports from `@modelcontextprotocol/sdk`
2. Zod schemas for tool inputs
3. Tool definitions with descriptions and schemas
4. Server setup with name and version
5. Tool implementation functions
6. Request handlers for tools and listing
Basic template:
```typescript
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
type CallToolRequest
} from "@modelcontextprotocol/sdk/types.js";
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
// Define your tool schemas
const YourToolSchema = z.object({
// ... your schema fields
});
const tools = {
your_tool: {
description: "Description of what your tool does",
inputSchema: zodToJsonSchema(YourToolSchema)
}
};
const server = new Server({
name: "@mcp-get-community/server-{name}",
version: "0.1.0",
author: "Michael Latman <https://michaellatman.com>"
}, {
capabilities: {
tools
}
});
// Implement your tool functionality
async function yourToolFunction(options: z.infer<typeof YourToolSchema>) {
// ... your implementation
}
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
try {
if (!request.params.arguments) {
throw new Error("Arguments are required");
}
switch (request.params.name) {
case "your_tool": {
const args = YourToolSchema.parse(request.params.arguments);
const result = await yourToolFunction(args);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error: unknown) {
if (error instanceof z.ZodError) {
const issues = error.issues.map((issue: z.ZodIssue) =>
`${issue.path.join('.')}: ${issue.message}`).join(', ');
throw new Error(`Invalid arguments: ${issues}`);
}
throw error;
}
});
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: Object.entries(tools).map(([name, tool]) => ({
name,
description: tool.description,
inputSchema: tool.inputSchema
}))
};
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Server running on stdio");
}
runServer().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
```
## Step 5: Documentation
Create a README.md with:
1. Description of your server
2. Features list
3. Installation instructions using `npx @michaellatman/mcp-get@latest install`
4. Configuration example for `mcpServers` in JSON
5. Tool documentation with parameter descriptions
6. Example requests and responses
7. Development instructions
Example structure:
```markdown
# @mcp-get-community/server-{name}
Description of your server.
## Features
- Feature 1
- Feature 2
- ...
## Installation
```bash
npx @michaellatman/mcp-get@latest install @mcp-get-community/server-{name}
```
## Usage
```json
{
"mcpServers": {
"@mcp-get-community/server-{name}": {
"runtime": "node",
"command": "npx",
"args": [
"-y",
"@mcp-get-community/server-{name}"
]
}
}
}
```
Tool documentation and examples...
```
## Step 6: License
Create a LICENSE file with the MIT license:
```
MIT License
Copyright (c) {year} Michael Latman <https://michaellatman.com>
Permission is hereby granted...
```
## Step 7: Update Root README
Add your server to the Available Servers section in the root README.md:
```markdown
- **[Your Server](src/server-{name})** - A brief description of what your server does.
```
## Step 8: Testing
1. Install dependencies: `npm install`
2. Build the project: `npm run build`
3. Test your server with actual LLM requests
4. Verify all TypeScript types are correct
5. Ensure error handling works properly
## Publishing
Once your server is ready:
1. Submit a pull request to this repository
2. Ensure all files are properly formatted and documented
3. Wait for review and approval
Your server will be automatically listed on the MCP Get registry once merged.
```
--------------------------------------------------------------------------------
/src/server-llm-txt/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
type CallToolRequest} from "@modelcontextprotocol/sdk/types.js";
import fetch from "node-fetch";
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import {
GetLLMTxtOptionsSchema,
ListLLMTxtOptionsSchema,
type LLMTxt,
type LLMTxtList,
type LLMTxtListItem
} from './schemas.js';
import { JSDOM } from 'jsdom';
import fs from 'fs';
import path from 'path';
import os from 'os';
// Define schema for search tool
const SearchLLMTxtOptionsSchema = z.object({
id: z.number().describe(
"The ID of the LLM.txt file to search in. Must be obtained first using the list_llm_txt command."
),
queries: z.array(z.string()).min(1).describe(
"Array of substrings to search for. Each query is searched case-insensitively. At least one query is required."
),
context_lines: z.number().optional().default(2).describe(
"Number of lines to show before and after each match for context. Defaults to 2 lines."
),
});
const tools = {
get_llm_txt: {
description: "Fetch an LLM.txt file from a given URL. Format your response in beautiful markdown.",
inputSchema: zodToJsonSchema(GetLLMTxtOptionsSchema)
},
list_llm_txt: {
description: "List available LLM.txt files from the directory. Use this first before fetching a specific LLM.txt file. Format your response in beautiful markdown.",
inputSchema: zodToJsonSchema(ListLLMTxtOptionsSchema)
},
search_llm_txt: {
description: "Search for multiple substrings in an LLM.txt file. Requires a valid ID obtained from list_llm_txt command. Returns snippets with page numbers for each match. Format your response in beautiful markdown, using code blocks for snippets.",
inputSchema: zodToJsonSchema(SearchLLMTxtOptionsSchema)
}
};
const server = new Server({
name: "server-llm-txt",
version: "0.1.0",
author: "Michael Latman (https://michaellatman.com)"
}, {
capabilities: {
tools
}
});
const DIRECTORY_URL = "https://directory.llmstxt.cloud/";
const MAX_RESPONSE_LENGTH = 100000;
const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours
// Get cache directory based on OS
function getCacheDir(): string {
const platform = os.platform();
let cacheDir: string;
switch (platform) {
case 'win32':
cacheDir = path.join(os.homedir(), 'AppData', 'Local', 'llm-txt-mcp');
break;
case 'darwin':
cacheDir = path.join(os.homedir(), 'Library', 'Caches', 'llm-txt-mcp');
break;
default: // linux and others
cacheDir = path.join(os.homedir(), '.cache', 'llm-txt-mcp');
}
// Ensure cache directory exists
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true });
}
return cacheDir;
}
// Cache management functions
function getCachedList(): LLMTxtList | null {
const cacheFile = path.join(getCacheDir(), 'llm-list-cache.json');
try {
if (!fs.existsSync(cacheFile)) {
return null;
}
const cacheData = JSON.parse(fs.readFileSync(cacheFile, 'utf-8'));
const now = Date.now();
if (now - cacheData.timestamp > CACHE_DURATION_MS) {
return null; // Cache expired
}
return cacheData.data;
} catch (error) {
console.error('Error reading cache:', error);
return null;
}
}
function saveToCache(data: LLMTxtList): void {
const cacheFile = path.join(getCacheDir(), 'llm-list-cache.json');
const cacheData = {
timestamp: Date.now(),
data
};
try {
fs.writeFileSync(cacheFile, JSON.stringify(cacheData), 'utf-8');
} catch (error) {
console.error('Error writing cache:', error);
}
}
// Simple hash function to convert string to number
function hashUrl(url: string): number {
let hash = 0;
for (let i = 0; i < url.length; i++) {
const char = url.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash); // Ensure positive number
}
async function getLLMTxt(id: number, page: number = 1): Promise<LLMTxt> {
// First get the list to find the URL for this ID
const allItems = await listLLMTxt(); // Get all items
const item = allItems.find(item => item.id === id);
if (!item) {
throw new Error(`No LLM.txt found with ID: ${id}`);
}
const response = await fetch(item.url, {
headers: {
"Accept": "text/plain",
"User-Agent": "llm-txt-mcp-server"
}
});
if (!response.ok) {
throw new Error(`Failed to fetch LLM.txt: ${response.statusText}`);
}
const fullContent = await response.text();
const start = (page - 1) * MAX_RESPONSE_LENGTH;
let content = fullContent.slice(start, start + MAX_RESPONSE_LENGTH);
const hasNextPage = start + content.length < fullContent.length;
return {
id: item.id,
url: item.url,
name: item.name,
description: item.description,
hasNextPage,
currentPage: page,
content,
};
}
// Cache the promise of fetching the list to prevent multiple concurrent fetches
let listFetchPromise: Promise<LLMTxtList> | null = null;
async function listLLMTxt(): Promise<LLMTxtList> {
// If we're already fetching, return that promise
if (listFetchPromise) {
return listFetchPromise;
}
// Check cache first
const cachedList = getCachedList();
if (cachedList) {
return cachedList;
}
// Create a new fetch promise
listFetchPromise = (async () => {
try {
const response = await fetch(DIRECTORY_URL, {
headers: {
"Accept": "text/html",
"User-Agent": "llm-txt-mcp-server"
}
});
if (!response.ok) {
throw new Error(`Failed to fetch LLM.txt list: ${response.statusText}`);
}
const html = await response.text();
const dom = new JSDOM(html);
const document = dom.window.document;
// Find all llm.txt links
const links: LLMTxtListItem[] = Array.from(document.querySelectorAll('li'))
.map(li => {
// Try to find llms-full.txt link first, fall back to llms.txt
const fullLink = li.querySelector('a[href*="llms-full.txt"]');
const link = fullLink || li.querySelector('a[href*="llms.txt"]');
if (!link) return null;
const url = link.getAttribute('href') || '';
return {
id: hashUrl(url),
url,
name: li.querySelector('h3')?.textContent?.trim() || 'Unknown',
description: li.querySelector('p')?.textContent?.trim() || ''
};
})
.filter((item): item is LLMTxtListItem => item !== null && item.url.endsWith('.txt'));
// Save to cache
saveToCache(links);
return links;
} finally {
// Clear the promise after it's done (success or failure)
listFetchPromise = null;
}
})();
return listFetchPromise;
}
async function searchLLMTxt(id: number, queries: string[], contextLines: number = 2): Promise<any> {
// First get the list to find the URL for this ID
const allItems = await listLLMTxt();
const item = allItems.find(item => item.id === id);
if (!item) {
throw new Error(`No LLM.txt found with ID: ${id}`);
}
const response = await fetch(item.url, {
headers: {
"Accept": "text/plain",
"User-Agent": "llm-txt-mcp-server"
}
});
if (!response.ok) {
throw new Error(`Failed to fetch LLM.txt: ${response.statusText}`);
}
const content = await response.text();
const lines = content.split('\n');
const results = [];
const searchRegexes = queries.map(query => new RegExp(query, 'gi'));
let charCount = 0;
for (let i = 0; i < lines.length; i++) {
const matches = searchRegexes.some(regex => regex.test(lines[i]));
if (matches) {
const startLine = Math.max(0, i - contextLines);
const endLine = Math.min(lines.length - 1, i + contextLines);
const snippet = lines.slice(startLine, endLine + 1).join('\n');
const page = Math.floor(charCount / MAX_RESPONSE_LENGTH) + 1;
// Find which queries matched this line
const matchedQueries = queries.filter(query =>
new RegExp(query, 'gi').test(lines[i])
);
results.push({
page,
lineNumber: i + 1,
snippet,
matchedLine: lines[i],
matchedQueries
});
}
charCount += lines[i].length + 1; // +1 for the newline character
}
return {
id: item.id,
url: item.url,
name: item.name,
matches: results
};
}
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
try {
if (!request.params.arguments) {
throw new Error("Arguments are required");
}
switch (request.params.name) {
case "get_llm_txt": {
const args = GetLLMTxtOptionsSchema.parse(request.params.arguments);
const result = await getLLMTxt(args.id, args.page);
// Always truncate to MAX_RESPONSE_LENGTH and indicate if there's more
const content = result.content.slice(0, MAX_RESPONSE_LENGTH);
const hasMoreContent = result.content.length > MAX_RESPONSE_LENGTH || result.hasNextPage;
const response = {
currentPage: result.currentPage,
...(hasMoreContent && {
hasNextPage: true
}),
content
};
return {
content: [{
type: "text",
text: JSON.stringify(response, null, 2)
}]
};
}
case "list_llm_txt": {
const result = await listLLMTxt();
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
}
case "search_llm_txt": {
const args = SearchLLMTxtOptionsSchema.parse(request.params.arguments);
const result = await searchLLMTxt(args.id, args.queries, args.context_lines);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error: unknown) {
if (error instanceof z.ZodError) {
const issues = error.issues.map((issue: z.ZodIssue) => `${issue.path.join('.')}: ${issue.message}`).join(', ');
throw new Error(`Invalid arguments: ${issues}`);
}
throw error;
}
});
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: Object.entries(tools).map(([name, tool]) => ({
name,
description: tool.description,
inputSchema: tool.inputSchema
}))
};
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("LLM.txt MCP Server running on stdio");
}
runServer().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});
```