diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28cba5a..7a10a22 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,105 +1,34 @@ # Contributing to MCP Servers -Thank you for your interest in contributing to the Model Context Protocol (MCP) servers! This document provides guidelines and instructions for contributing. +Thanks for your interest in contributing! Here's how you can help make this repo better. -## Types of Contributions +We accept changes through [the standard GitHub flow model](https://docs.github.com/en/get-started/using-github/github-flow). -### 1. New Servers +## Server Listings -The repository contains reference implementations, as well as a list of community servers. -We generally don't accept new servers into the repository. We do accept pull requests to the [README.md](./README.md) -adding a reference to your servers. +We welcome PRs that add links to your servers in the [README.md](./README.md)! -Please keep lists in alphabetical order to minimize merge conflicts when adding new items. +## Server Implementations -- Check the [modelcontextprotocol.io](https://modelcontextprotocol.io) documentation -- Ensure your server doesn't duplicate existing functionality -- Consider whether your server would be generally useful to others -- Follow [security best practices](https://modelcontextprotocol.io/docs/concepts/transports#security-considerations) from the MCP documentation -- Create a PR adding a link to your server to the [README.md](./README.md). +We welcome: +- **Bug fixes** — Help us squash those pesky bugs. +- **Usability improvements** — Making servers easier to use for humans and agents. +- **Enhancements that demonstrate MCP protocol features** — We encourage contributions that help reference servers better illustrate underutilized aspects of the MCP protocol beyond just Tools, such as Resources, Prompts, or Roots. For example, adding Roots support to filesystem-server helps showcase this important but lesser-known feature. -### 2. Improvements to Existing Servers -Enhancements to existing servers are welcome! This includes: +We're more selective about: +- **Other new features** — Especially if they're not crucial to the server's core purpose or are highly opinionated. The existing servers are reference servers meant to inspire the community. If you need specific features, we encourage you to build enhanced versions! We think a diverse ecosystem of servers is beneficial for everyone, and would love to link to your improved server in our README. -- Bug fixes -- Performance improvements -- New features -- Security enhancements +We don't accept: +- **New server implementations** — We encourage you to publish them yourself, and link to them from the README. -### 3. Documentation -Documentation improvements are always welcome: +## Documentation -- Fixing typos or unclear instructions -- Adding examples -- Improving setup instructions -- Adding troubleshooting guides +Improvements to existing documentation is welcome - although generally we'd prefer ergonomic improvements than documenting pain points if possible! -## Getting Started - -1. Fork the repository -2. Clone your fork: - ```bash - git clone https://github.com/your-username/servers.git - ``` -3. Add the upstream remote: - ```bash - git remote add upstream https://github.com/modelcontextprotocol/servers.git - ``` -4. Create a branch: - ```bash - git checkout -b my-feature - ``` - -## Development Guidelines - -### Code Style -- Follow the existing code style in the repository -- Include appropriate type definitions -- Add comments for complex logic - -### Documentation -- Include a detailed README.md in your server directory -- Document all configuration options -- Provide setup instructions -- Include usage examples - -### Security -- Follow security best practices -- Implement proper input validation -- Handle errors appropriately -- Document security considerations - -## Submitting Changes - -1. Commit your changes: - ```bash - git add . - git commit -m "Description of changes" - ``` -2. Push to your fork: - ```bash - git push origin my-feature - ``` -3. Create a Pull Request through GitHub - -### Pull Request Guidelines - -- Thoroughly test your changes -- Fill out the pull request template completely -- Link any related issues -- Provide clear description of changes -- Include any necessary documentation updates -- Add screenshots for UI changes -- List any breaking changes +We're more selective about adding wholly new documentation, especially in ways that aren't vendor neutral (e.g. how to run a particular server with a particular client). ## Community -- Participate in [GitHub Discussions](https://github.com/orgs/modelcontextprotocol/discussions) -- Follow the [Code of Conduct](CODE_OF_CONDUCT.md) +[Learn how the MCP community communicates](https://modelcontextprotocol.io/community/communication). -## Questions? - -- Check the [documentation](https://modelcontextprotocol.io) -- Ask in GitHub Discussions - -Thank you for contributing to MCP Servers! +Thank you for helping make MCP servers better for everyone! \ No newline at end of file diff --git a/README.md b/README.md index e230c71..63039d1 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,8 @@ Official integrations are maintained by companies building production ready MCP - Linear Logo **[Linear](https://linear.app/docs/mcp)** - Search, create, and update Linear issues, projects, and comments. - Lingo.dev Logo **[Lingo.dev](https://github.com/lingodotdev/lingo.dev/blob/main/mcp.md)** - Make your AI agent speak every language on the planet, using [Lingo.dev](https://lingo.dev) Localization Engine. - LiGo Logo **[LinkedIn MCP Runner](https://github.com/ertiqah/linkedin-mcp-runner)** - Write, edit, and schedule LinkedIn posts right from ChatGPT and Claude with [LiGo](https://ligo.ertiqah.com/). +- Linkup Logo **[Linkup](https://github.com/LinkupPlatform/js-mcp-server)** - (JS version) MCP server that provides web search capabilities through Linkup's advanced search API. This server enables AI assistants and development tools to perform intelligent web searches with natural language queries. +- Linkup Logo **[Linkup](https://github.com/LinkupPlatform/python-mcp-server)** - (Python version) MCP server that provides web search capabilities through Linkup's advanced search API. This server enables AI assistants and development tools to perform intelligent web searches with natural language queries. - Lisply **[Lisply](https://github.com/gornskew/lisply-mcp)** - Flexible frontend for compliant Lisp-speaking backends. - Litmus.io Logo **[Litmus.io](https://github.com/litmusautomation/litmus-mcp-server)** - Official MCP server for configuring [Litmus](https://litmus.io) Edge for Industrial Data Collection, Edge Analytics & Industrial AI. - Liveblocks Logo **[Liveblocks](https://github.com/liveblocks/liveblocks-mcp-server)** - Ready‑made features for AI & human collaboration—use this to develop your [Liveblocks](https://liveblocks.io) app quicker. @@ -412,6 +414,7 @@ Official integrations are maintained by companies building production ready MCP - TiDB Logo **[TiDB](https://github.com/pingcap/pytidb)** - MCP Server to interact with TiDB database platform. - Tinybird Logo **[Tinybird](https://github.com/tinybirdco/mcp-tinybird)** - Interact with Tinybird serverless ClickHouse platform - Tldv Logo **[Tldv](https://gitlab.com/tldv/tldv-mcp-server)** - Connect your AI agents to Google-Meet, Zoom & Microsoft Teams through [tl;dv](https://tldv.io) +- Todoist Logo **[Todoist](https://github.com/doist/todoist-ai)** - Search, add, and update [Todoist](https://todoist.com) tasks, projects, sections, comments, and more. - Token Metrics Logo **[Token Metrics](https://github.com/token-metrics/mcp)** - [Token Metrics](https://www.tokenmetrics.com/) integration for fetching real-time crypto market data, trading signals, price predictions, and advanced analytics. - TomTom Logo **[TomTom-MCP](https://github.com/tomtom-international/tomtom-mcp)** - The [TomTom](https://www.tomtom.com/) MCP Server simplifies geospatial development by providing seamless access to TomTom's location services, including search, routing, traffic and static maps data. - Trade Agent Logo **[Trade Agent](https://github.com/Trade-Agent/trade-agent-mcp)** - Execute stock and crypto trades on your brokerage via [Trade Agent](https://thetradeagent.ai) @@ -573,6 +576,7 @@ A growing set of community-developed and maintained servers demonstrates various - **[coin_api_mcp](https://github.com/longmans/coin_api_mcp)** - Provides access to [coinmarketcap](https://coinmarketcap.com/) cryptocurrency data. - **[CoinMarketCap](https://github.com/shinzo-labs/coinmarketcap-mcp)** - Implements the complete [CoinMarketCap](https://coinmarketcap.com/) API for accessing cryptocurrency market data, exchange information, and other blockchain-related metrics. - **[commands](https://github.com/g0t4/mcp-server-commands)** - Run commands and scripts. Just like in a terminal. +- **[Companies House MCP](https://github.com/stefanoamorelli/companies-house-mcp)** (by Stefano Amorelli) - MCP server to connect with the UK Companies House API. - **[computer-control-mcp](https://github.com/AB498/computer-control-mcp)** - MCP server that provides computer control capabilities, like mouse, keyboard, OCR, etc. using PyAutoGUI, RapidOCR, ONNXRuntime Without External Dependencies. - **[Computer-Use - Remote MacOS Use](https://github.com/baryhuang/mcp-remote-macos-use)** - Open-source out-of-the-box alternative to OpenAI Operator, providing a full desktop experience and optimized for using remote macOS machines as autonomous AI agents. - **[Congress.gov API](https://github.com/AshwinSundar/congress_gov_mcp)** - An MCP server to interact with real-time data from the Congress.gov API, which is the official API for the United States Congress. @@ -632,6 +636,7 @@ A growing set of community-developed and maintained servers demonstrates various - **[Docker](https://github.com/ckreiling/mcp-server-docker)** - Integrate with Docker to manage containers, images, volumes, and networks. - **[Docker](https://github.com/0xshariq/docker-mcp-server)** - Docker MCP Server provides advanced, unified Docker management via CLI and MCP workflows, supporting containers, images, volumes, networks, and orchestration. - **[Docs](https://github.com/da1z/docsmcp)** - Enable documentation access for the AI agent, supporting llms.txt and other remote or local files. +- **[documcp](https://github.com/tosin2013/documcp)** - An MCP server for intelligent document processing and management, supporting multiple formats and document operations. - **[Docy](https://github.com/oborchers/mcp-server-docy)** - Docy gives your AI direct access to the technical documentation it needs, right when it needs it. No more outdated information, broken links, or rate limits - just accurate, real-time documentation access for more precise coding assistance. - **[Dodo Payments](https://github.com/dodopayments/dodopayments-node/tree/main/packages/mcp-server)** - Enables AI agents to securely perform payment operations via a lightweight, serverless-compatible interface to the [Dodo Payments](https://dodopayments.com) API. - **[Domain Tools](https://github.com/deshabhishek007/domain-tools-mcp-server)** - A Model Context Protocol (MCP) server for comprehensive domain analysis: WHOIS, DNS records, and DNS health checks. @@ -1153,6 +1158,7 @@ A growing set of community-developed and maintained servers demonstrates various - **[Trello MCP Server](https://github.com/lioarce01/trello-mcp-server)** - An MCP server that interact with user Trello boards, modifying them with prompting. - **[Trino](https://github.com/tuannvm/mcp-trino)** - A high-performance Model Context Protocol (MCP) server for Trino implemented in Go. - **[Tripadvisor](https://github.com/pab1it0/tripadvisor-mcp)** - An MCP server that enables LLMs to interact with Tripadvisor API, supporting location data, reviews, and photos through standardized MCP interfaces +- **[Triplyfy MCP](https://github.com/helpful-AIs/triplyfy-mcp)** - An MCP server that lets LLMs plan and manage itineraries with interactive maps in Triplyfy; manage itineraries, places and notes, and search/save flights. - **[TrueNAS Core MCP](https://github.com/vespo92/TrueNasCoreMCP)** - An MCP server for interacting with TrueNAS Core. - **[TuriX Computer Automation MCP](https://github.com/TurixAI/TuriX-CUA/tree/mac_mcp)** - MCP server for helping automation control your computer complete your pre-setting task. - **[Tyk API Management](https://github.com/TykTechnologies/tyk-dashboard-mcp)** - Chat with all of your organization's managed APIs and perform other API lifecycle operations, managing tokens, users, analytics, and more. diff --git a/src/filesystem/README.md b/src/filesystem/README.md index 0f456f3..499fca5 100644 --- a/src/filesystem/README.md +++ b/src/filesystem/README.md @@ -145,14 +145,27 @@ The server's directory access control follows this flow: - Fails if destination exists - **search_files** - - Recursively search for files/directories + - Recursively search for files/directories that match or do not match patterns - Inputs: - `path` (string): Starting directory - `pattern` (string): Search pattern - - `excludePatterns` (string[]): Exclude any patterns. Glob formats are supported. - - Case-insensitive matching + - `excludePatterns` (string[]): Exclude any patterns. + - Glob-style pattern matching - Returns full paths to matches +- **directory_tree** + - Get recursive JSON tree structure of directory contents + - Inputs: + - `path` (string): Starting directory + - `excludePatterns` (string[]): Exclude any patterns. Glob formats are supported. + - Returns: + - JSON array where each entry contains: + - `name` (string): File/directory name + - `type` ('file'|'directory'): Entry type + - `children` (array): Present only for directories + - Empty array for empty directories + - Omitted for files + - **get_file_info** - Get detailed file/directory metadata - Input: `path` (string) diff --git a/src/filesystem/__tests__/directory-tree.test.ts b/src/filesystem/__tests__/directory-tree.test.ts new file mode 100644 index 0000000..6828650 --- /dev/null +++ b/src/filesystem/__tests__/directory-tree.test.ts @@ -0,0 +1,147 @@ +import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; +import * as fs from 'fs/promises'; +import * as path from 'path'; +import * as os from 'os'; + +// We need to test the buildTree function, but it's defined inside the request handler +// So we'll extract the core logic into a testable function +import { minimatch } from 'minimatch'; + +interface TreeEntry { + name: string; + type: 'file' | 'directory'; + children?: TreeEntry[]; +} + +async function buildTreeForTesting(currentPath: string, rootPath: string, excludePatterns: string[] = []): Promise { + const entries = await fs.readdir(currentPath, {withFileTypes: true}); + const result: TreeEntry[] = []; + + for (const entry of entries) { + const relativePath = path.relative(rootPath, path.join(currentPath, entry.name)); + const shouldExclude = excludePatterns.some(pattern => { + if (pattern.includes('*')) { + return minimatch(relativePath, pattern, {dot: true}); + } + // For files: match exact name or as part of path + // For directories: match as directory path + return minimatch(relativePath, pattern, {dot: true}) || + minimatch(relativePath, `**/${pattern}`, {dot: true}) || + minimatch(relativePath, `**/${pattern}/**`, {dot: true}); + }); + if (shouldExclude) + continue; + + const entryData: TreeEntry = { + name: entry.name, + type: entry.isDirectory() ? 'directory' : 'file' + }; + + if (entry.isDirectory()) { + const subPath = path.join(currentPath, entry.name); + entryData.children = await buildTreeForTesting(subPath, rootPath, excludePatterns); + } + + result.push(entryData); + } + + return result; +} + +describe('buildTree exclude patterns', () => { + let testDir: string; + + beforeEach(async () => { + testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'filesystem-test-')); + + // Create test directory structure + await fs.mkdir(path.join(testDir, 'src')); + await fs.mkdir(path.join(testDir, 'node_modules')); + await fs.mkdir(path.join(testDir, '.git')); + await fs.mkdir(path.join(testDir, 'nested', 'node_modules'), { recursive: true }); + + // Create test files + await fs.writeFile(path.join(testDir, '.env'), 'SECRET=value'); + await fs.writeFile(path.join(testDir, '.env.local'), 'LOCAL_SECRET=value'); + await fs.writeFile(path.join(testDir, 'src', 'index.js'), 'console.log("hello");'); + await fs.writeFile(path.join(testDir, 'package.json'), '{}'); + await fs.writeFile(path.join(testDir, 'node_modules', 'module.js'), 'module.exports = {};'); + await fs.writeFile(path.join(testDir, 'nested', 'node_modules', 'deep.js'), 'module.exports = {};'); + }); + + afterEach(async () => { + await fs.rm(testDir, { recursive: true, force: true }); + }); + + it('should exclude files matching simple patterns', async () => { + // Test the current implementation - this will fail until the bug is fixed + const tree = await buildTreeForTesting(testDir, testDir, ['.env']); + const fileNames = tree.map(entry => entry.name); + + expect(fileNames).not.toContain('.env'); + expect(fileNames).toContain('.env.local'); // Should not exclude this + expect(fileNames).toContain('src'); + expect(fileNames).toContain('package.json'); + }); + + it('should exclude directories matching simple patterns', async () => { + const tree = await buildTreeForTesting(testDir, testDir, ['node_modules']); + const dirNames = tree.map(entry => entry.name); + + expect(dirNames).not.toContain('node_modules'); + expect(dirNames).toContain('src'); + expect(dirNames).toContain('.git'); + }); + + it('should exclude nested directories with same pattern', async () => { + const tree = await buildTreeForTesting(testDir, testDir, ['node_modules']); + + // Find the nested directory + const nestedDir = tree.find(entry => entry.name === 'nested'); + expect(nestedDir).toBeDefined(); + expect(nestedDir!.children).toBeDefined(); + + // The nested/node_modules should also be excluded + const nestedChildren = nestedDir!.children!.map(child => child.name); + expect(nestedChildren).not.toContain('node_modules'); + }); + + it('should handle glob patterns correctly', async () => { + const tree = await buildTreeForTesting(testDir, testDir, ['*.env']); + const fileNames = tree.map(entry => entry.name); + + expect(fileNames).not.toContain('.env'); + expect(fileNames).toContain('.env.local'); // *.env should not match .env.local + expect(fileNames).toContain('src'); + }); + + it('should handle dot files correctly', async () => { + const tree = await buildTreeForTesting(testDir, testDir, ['.git']); + const dirNames = tree.map(entry => entry.name); + + expect(dirNames).not.toContain('.git'); + expect(dirNames).toContain('.env'); // Should not exclude this + }); + + it('should work with multiple exclude patterns', async () => { + const tree = await buildTreeForTesting(testDir, testDir, ['node_modules', '.env', '.git']); + const entryNames = tree.map(entry => entry.name); + + expect(entryNames).not.toContain('node_modules'); + expect(entryNames).not.toContain('.env'); + expect(entryNames).not.toContain('.git'); + expect(entryNames).toContain('src'); + expect(entryNames).toContain('package.json'); + }); + + it('should handle empty exclude patterns', async () => { + const tree = await buildTreeForTesting(testDir, testDir, []); + const entryNames = tree.map(entry => entry.name); + + // All entries should be included + expect(entryNames).toContain('node_modules'); + expect(entryNames).toContain('.env'); + expect(entryNames).toContain('.git'); + expect(entryNames).toContain('src'); + }); +}); \ No newline at end of file diff --git a/src/filesystem/__tests__/lib.test.ts b/src/filesystem/__tests__/lib.test.ts index 76d3679..cc13ef0 100644 --- a/src/filesystem/__tests__/lib.test.ts +++ b/src/filesystem/__tests__/lib.test.ts @@ -316,7 +316,7 @@ describe('Lib Functions', () => { const result = await searchFilesWithValidation( testDir, - 'test', + '*test*', allowedDirs, { excludePatterns: ['*.log', 'node_modules'] } ); @@ -346,7 +346,7 @@ describe('Lib Functions', () => { const result = await searchFilesWithValidation( testDir, - 'test', + '*test*', allowedDirs, {} ); @@ -370,7 +370,7 @@ describe('Lib Functions', () => { const result = await searchFilesWithValidation( testDir, - 'test', + '*test*', allowedDirs, { excludePatterns: ['*.backup'] } ); diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 310cfa6..7888196 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -14,6 +14,7 @@ import { createReadStream } from "fs"; import path from "path"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; +import { minimatch } from "minimatch"; import { normalizePath, expandHome } from './path-utils.js'; import { getValidRootDirectories } from './roots-utils.js'; import { @@ -87,7 +88,10 @@ const ReadMediaFileArgsSchema = z.object({ }); const ReadMultipleFilesArgsSchema = z.object({ - paths: z.array(z.string()), + paths: z + .array(z.string()) + .min(1, "At least one file path must be provided") + .describe("Array of file paths to read. Each path must be a string pointing to a valid file within allowed directories."), }); const WriteFileArgsSchema = z.object({ @@ -121,6 +125,7 @@ const ListDirectoryWithSizesArgsSchema = z.object({ const DirectoryTreeArgsSchema = z.object({ path: z.string(), + excludePatterns: z.array(z.string()).optional().default([]) }); const MoveFileArgsSchema = z.object({ @@ -275,9 +280,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { name: "search_files", description: "Recursively search for files and directories matching a pattern. " + - "Searches through all subdirectories from the starting path. The search " + - "is case-insensitive and matches partial names. Returns full paths to all " + - "matching items. Great for finding files when you don't know their exact location. " + + "The patterns should be glob-style patterns that match paths relative to the working directory. " + + "Use pattern like '*.ext' to match files in current directory, and '**/*.ext' to match files in all subdirectories. " + + "Returns full paths to all matching items. Great for finding files when you don't know their exact location. " + "Only searches within allowed directories.", inputSchema: zodToJsonSchema(SearchFilesArgsSchema) as ToolInput, }, @@ -528,13 +533,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { type: 'file' | 'directory'; children?: TreeEntry[]; } + const rootPath = parsed.data.path; - async function buildTree(currentPath: string): Promise { + async function buildTree(currentPath: string, excludePatterns: string[] = []): Promise { const validPath = await validatePath(currentPath); const entries = await fs.readdir(validPath, {withFileTypes: true}); const result: TreeEntry[] = []; for (const entry of entries) { + const relativePath = path.relative(rootPath, path.join(currentPath, entry.name)); + const shouldExclude = excludePatterns.some(pattern => { + if (pattern.includes('*')) { + return minimatch(relativePath, pattern, {dot: true}); + } + // For files: match exact name or as part of path + // For directories: match as directory path + return minimatch(relativePath, pattern, {dot: true}) || + minimatch(relativePath, `**/${pattern}`, {dot: true}) || + minimatch(relativePath, `**/${pattern}/**`, {dot: true}); + }); + if (shouldExclude) + continue; + const entryData: TreeEntry = { name: entry.name, type: entry.isDirectory() ? 'directory' : 'file' @@ -542,7 +562,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (entry.isDirectory()) { const subPath = path.join(currentPath, entry.name); - entryData.children = await buildTree(subPath); + entryData.children = await buildTree(subPath, excludePatterns); } result.push(entryData); @@ -551,7 +571,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { return result; } - const treeData = await buildTree(parsed.data.path); + const treeData = await buildTree(rootPath, parsed.data.excludePatterns); return { content: [{ type: "text", diff --git a/src/filesystem/lib.ts b/src/filesystem/lib.ts index 40cb316..240ca0d 100644 --- a/src/filesystem/lib.ts +++ b/src/filesystem/lib.ts @@ -367,14 +367,14 @@ export async function searchFilesWithValidation( await validatePath(fullPath); const relativePath = path.relative(rootPath, fullPath); - const shouldExclude = excludePatterns.some(excludePattern => { - const globPattern = excludePattern.includes('*') ? excludePattern : `**/${excludePattern}/**`; - return minimatch(relativePath, globPattern, { dot: true }); - }); + const shouldExclude = excludePatterns.some(excludePattern => + minimatch(relativePath, excludePattern, { dot: true }) + ); if (shouldExclude) continue; - if (entry.name.toLowerCase().includes(pattern.toLowerCase())) { + // Use glob matching for the search pattern + if (minimatch(relativePath, pattern, { dot: true })) { results.push(fullPath); } diff --git a/src/git/README.md b/src/git/README.md index 6ea2e8c..c56ef50 100644 --- a/src/git/README.md +++ b/src/git/README.md @@ -57,10 +57,12 @@ Please note that mcp-server-git is currently in early development. The functiona - Returns: Confirmation of reset operation 8. `git_log` - - Shows the commit logs + - Shows the commit logs with optional date filtering - Inputs: - `repo_path` (string): Path to Git repository - `max_count` (number, optional): Maximum number of commits to show (default: 10) + - `start_timestamp` (string, optional): Start timestamp for filtering commits. Accepts ISO 8601 format (e.g., '2024-01-15T14:30:25'), relative dates (e.g., '2 weeks ago', 'yesterday'), or absolute dates (e.g., '2024-01-15', 'Jan 15 2024') + - `end_timestamp` (string, optional): End timestamp for filtering commits. Accepts ISO 8601 format (e.g., '2024-01-15T14:30:25'), relative dates (e.g., '2 weeks ago', 'yesterday'), or absolute dates (e.g., '2024-01-15', 'Jan 15 2024') - Returns: Array of commit entries with hash, author, date, and message 9. `git_create_branch` diff --git a/src/git/src/mcp_server_git/server.py b/src/git/src/mcp_server_git/server.py index f1c4e83..a16b601 100644 --- a/src/git/src/mcp_server_git/server.py +++ b/src/git/src/mcp_server_git/server.py @@ -48,6 +48,14 @@ class GitReset(BaseModel): class GitLog(BaseModel): repo_path: str max_count: int = 10 + start_timestamp: Optional[str] = Field( + None, + description="Start timestamp for filtering commits. Accepts: ISO 8601 format (e.g., '2024-01-15T14:30:25'), relative dates (e.g., '2 weeks ago', 'yesterday'), or absolute dates (e.g., '2024-01-15', 'Jan 15 2024')" + ) + end_timestamp: Optional[str] = Field( + None, + description="End timestamp for filtering commits. Accepts: ISO 8601 format (e.g., '2024-01-15T14:30:25'), relative dates (e.g., '2 weeks ago', 'yesterday'), or absolute dates (e.g., '2024-01-15', 'Jan 15 2024')" + ) class GitCreateBranch(BaseModel): repo_path: str @@ -83,6 +91,7 @@ class GitBranch(BaseModel): description="The commit sha that branch should NOT contain. Do not pass anything to this param if no commit sha is specified", ) + class GitTools(str, Enum): STATUS = "git_status" DIFF_UNSTAGED = "git_diff_unstaged" @@ -125,17 +134,41 @@ def git_reset(repo: git.Repo) -> str: repo.index.reset() return "All staged changes reset" -def git_log(repo: git.Repo, max_count: int = 10) -> list[str]: - commits = list(repo.iter_commits(max_count=max_count)) - log = [] - for commit in commits: - log.append( - f"Commit: {commit.hexsha!r}\n" - f"Author: {commit.author!r}\n" - f"Date: {commit.authored_datetime}\n" - f"Message: {commit.message!r}\n" - ) - return log +def git_log(repo: git.Repo, max_count: int = 10, start_timestamp: Optional[str] = None, end_timestamp: Optional[str] = None) -> list[str]: + if start_timestamp or end_timestamp: + # Use git log command with date filtering + args = [] + if start_timestamp: + args.extend(['--since', start_timestamp]) + if end_timestamp: + args.extend(['--until', end_timestamp]) + args.extend(['--format=%H%n%an%n%ad%n%s%n']) + + log_output = repo.git.log(*args).split('\n') + + log = [] + # Process commits in groups of 4 (hash, author, date, message) + for i in range(0, len(log_output), 4): + if i + 3 < len(log_output) and len(log) < max_count: + log.append( + f"Commit: {log_output[i]}\n" + f"Author: {log_output[i+1]}\n" + f"Date: {log_output[i+2]}\n" + f"Message: {log_output[i+3]}\n" + ) + return log + else: + # Use existing logic for simple log without date filtering + commits = list(repo.iter_commits(max_count=max_count)) + log = [] + for commit in commits: + log.append( + f"Commit: {commit.hexsha!r}\n" + f"Author: {commit.author!r}\n" + f"Date: {commit.authored_datetime}\n" + f"Message: {commit.message!r}\n" + ) + return log def git_create_branch(repo: git.Repo, branch_name: str, base_branch: str | None = None) -> str: if base_branch: @@ -203,6 +236,7 @@ def git_branch(repo: git.Repo, branch_type: str, contains: str | None = None, no return branch_info + async def serve(repository: Path | None) -> None: logger = logging.getLogger(__name__) @@ -283,6 +317,7 @@ async def serve(repository: Path | None) -> None: name=GitTools.BRANCH, description="List Git branches", inputSchema=GitBranch.model_json_schema(), + ) ] @@ -380,13 +415,19 @@ async def serve(repository: Path | None) -> None: text=result )] + # Update the LOG case: case GitTools.LOG: - log = git_log(repo, arguments.get("max_count", 10)) + log = git_log( + repo, + arguments.get("max_count", 10), + arguments.get("start_timestamp"), + arguments.get("end_timestamp") + ) return [TextContent( type="text", text="Commit history:\n" + "\n".join(log) )] - + case GitTools.CREATE_BRANCH: result = git_create_branch( repo, @@ -423,7 +464,7 @@ async def serve(repository: Path | None) -> None: type="text", text=result )] - + case _: raise ValueError(f"Unknown tool: {name}") diff --git a/src/sequentialthinking/index.ts b/src/sequentialthinking/index.ts index bd486fd..3498697 100644 --- a/src/sequentialthinking/index.ts +++ b/src/sequentialthinking/index.ts @@ -206,12 +206,12 @@ You should: }, thoughtNumber: { type: "integer", - description: "Current thought number", + description: "Current thought number (numeric value, e.g., 1, 2, 3)", minimum: 1 }, totalThoughts: { type: "integer", - description: "Estimated total thoughts needed", + description: "Estimated total thoughts needed (numeric value, e.g., 5, 10)", minimum: 1 }, isRevision: {