mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-02-19 11:54:58 -05:00
Merge branch 'main' into ajoslin/memory
This commit is contained in:
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -104,6 +104,7 @@ jobs:
|
||||
|
||||
publish-pypi:
|
||||
needs: [update-packages, create-metadata]
|
||||
if: ${{ needs.create-metadata.outputs.pypi_packages != '[]' && needs.create-metadata.outputs.pypi_packages != '' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -145,6 +146,7 @@ jobs:
|
||||
|
||||
publish-npm:
|
||||
needs: [update-packages, create-metadata]
|
||||
if: ${{ needs.create-metadata.outputs.npm_packages != '[]' && needs.create-metadata.outputs.npm_packages != '' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
25
README.md
25
README.md
@@ -5,12 +5,16 @@ to community built servers and additional resources.
|
||||
|
||||
The servers in this repository showcase the versatility and extensibility of MCP, demonstrating how it can be used to give Large Language Models (LLMs) secure, controlled access to tools and data sources.
|
||||
Typically, each MCP server is implemented with an MCP SDK:
|
||||
|
||||
- [C# MCP SDK](https://github.com/modelcontextprotocol/csharp-sdk)
|
||||
- [Go MCP SDK](https://github.com/modelcontextprotocol/go-sdk)
|
||||
- [Java MCP SDK](https://github.com/modelcontextprotocol/java-sdk)
|
||||
- [Kotlin MCP SDK](https://github.com/modelcontextprotocol/kotlin-sdk)
|
||||
- [Python MCP SDK](https://github.com/modelcontextprotocol/python-sdk)
|
||||
- [Typescript MCP SDK](https://github.com/modelcontextprotocol/typescript-sdk)
|
||||
- [Ruby MCP SDK](https://github.com/modelcontextprotocol/ruby-sdk)
|
||||
- [Rust MCP SDK](https://github.com/modelcontextprotocol/rust-sdk)
|
||||
- [Swift MCP SDK](https://github.com/modelcontextprotocol/swift-sdk)
|
||||
- [TypeScript MCP SDK](https://github.com/modelcontextprotocol/typescript-sdk)
|
||||
|
||||
> Note: Lists in this README are maintained in alphabetical order to minimize merge conflicts when adding new items.
|
||||
|
||||
@@ -105,7 +109,7 @@ Official integrations are maintained by companies building production ready MCP
|
||||
- <img height="12" width="12" src="https://browserstack.wpenginepowered.com/wp-content/themes/browserstack/img/favicons/favicon.ico" alt="BrowserStack Logo" /> **[BrowserStack](https://github.com/browserstack/mcp-server)** - Access BrowserStack's [Test Platform](https://www.browserstack.com/test-platform) to debug, write and fix tests, do accessibility testing and more.
|
||||
- <img height="12" width="12" src="https://cdn.prod.website-files.com/65c0b8763c04cd15daa89b20/671f9d1301ac85495013761d_Favicon-White.png" alt="Bucket" /> **[Bucket](https://github.com/bucketco/bucket-javascript-sdk/tree/main/packages/cli#model-context-protocol)** - Flag features, manage company data, and control feature access using [Bucket](https://bucket.co)
|
||||
- <img height="12" width="12" src="https://www.google.com/s2/favicons?domain=buildkite.com&sz=24" alt="Buildkite Logo" /> **[Buildkite](https://github.com/buildkite/buildkite-mcp-server)** - Exposing Buildkite data (pipelines, builds, jobs, tests) to AI tooling and editors.
|
||||
- <img height="12" width="12" src="https://bldbl.dev/favico.png" alt="Buildable Logo" />**[Buildable](https://github.com/chunkydotdev/bldbl-mcp)** (Typescript) - Official MCP server for Buildable AI-powered development platform. Enables AI assistants to manage tasks, track progress, get project context, and collaborate with humans on software projects.
|
||||
- <img height="12" width="12" src="https://bldbl.dev/favico.png" alt="Buildable Logo" />**[Buildable](https://github.com/chunkydotdev/bldbl-mcp)** (TypeScript) - Official MCP server for Buildable AI-powered development platform. Enables AI assistants to manage tasks, track progress, get project context, and collaborate with humans on software projects.
|
||||
- <img height="12" width="12" src="https://builtwith.com/favicon.ico" alt="BuiltWith Logo" /> **[BuiltWith](https://github.com/builtwith/mcp)** - Identify the technology stack behind any website.
|
||||
- <img height="12" width="12" src="https://portswigger.net/favicon.ico" alt="PortSwigger Logo" /> **[Burp Suite](https://github.com/PortSwigger/mcp-server)** - MCP Server extension allowing AI clients to connect to [Burp Suite](https://portswigger.net)
|
||||
- <img height="12" width="12" src="https://campertunity.com/assets/icon/favicon.ico" alt="Campertunity Logo" /> **[Campertunity](https://github.com/campertunity/mcp-server)** - Search campgrounds around the world on campertunity, check availability, and provide booking links.
|
||||
@@ -228,7 +232,7 @@ Official integrations are maintained by companies building production ready MCP
|
||||
- <img height="12" width="12" src="https://linear.app/favicon.ico" alt="Linear Logo" /> **[Linear](https://linear.app/docs/mcp)** - Search, create, and update Linear issues, projects, and comments.
|
||||
- <img height="12" width="12" src="https://lingo.dev/favicon.ico" alt="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.
|
||||
- <img height="12" width="12" src="https://ligo.ertiqah.com/favicon.avif" alt="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/).
|
||||
- <img src="https://gornschool.com/favicon.ico" alt="Lisply" width="12" height="12"> **[Lisply](https://github.com/gornskew/lisply-mcp)** - Flexible frontend for compliant Lisp-speaking backends.
|
||||
- <img src="https://gornschool.com/gorn.png" alt="Lisply" width="12" height="12"> **[Lisply](https://github.com/gornskew/lisply-mcp)** - Flexible frontend for compliant Lisp-speaking backends.
|
||||
- <img height="12" width="12" src="https://litmus.io/favicon.ico" alt="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.
|
||||
- <img height="12" width="12" src="https://liveblocks.io/favicon.ico" alt="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.
|
||||
- <img height="12" width="12" src="https://logfire.pydantic.dev/favicon.ico" alt="Logfire Logo" /> **[Logfire](https://github.com/pydantic/logfire-mcp)** - Provides access to OpenTelemetry traces and metrics through Logfire.
|
||||
@@ -241,7 +245,6 @@ Official integrations are maintained by companies building production ready MCP
|
||||
- <img height="14" width="14" src="https://raw.githubusercontent.com/rust-mcp-stack/mcp-discovery/refs/heads/main/docs/_media/mcp-discovery-logo.png" alt="mcp-discovery logo" /> **[MCP Discovery](https://github.com/rust-mcp-stack/mcp-discovery)** - A lightweight CLI tool built in Rust for discovering MCP server capabilities.
|
||||
- <img height="12" width="12" src="https://googleapis.github.io/genai-toolbox/favicons/favicon.ico" alt="MCP Toolbox for Databases Logo" /> **[MCP Toolbox for Databases](https://github.com/googleapis/genai-toolbox)** - Open source MCP server specializing in easy, fast, and secure tools for Databases. Supports AlloyDB, BigQuery, Bigtable, Cloud SQL, Dgraph, MySQL, Neo4j, Postgres, Spanner, and more.
|
||||
- <img height="12" width="12" src="https://www.meilisearch.com/favicon.ico" alt="Meilisearch Logo" /> **[Meilisearch](https://github.com/meilisearch/meilisearch-mcp)** - Interact & query with Meilisearch (Full-text & semantic search API)
|
||||
- <img height="12" width="12" src="https://memgraph.com/favicon.png" alt="Memgraph Logo" /> **[Memgraph](https://github.com/memgraph/mcp-memgraph)** - Query your data in [Memgraph](https://memgraph.com/) graph database.
|
||||
- <img height="12" width="12" src="https://memgraph.com/favicon.png" alt="Memgraph Logo" /> **[Memgraph](https://github.com/memgraph/ai-toolkit/tree/main/integrations/mcp-memgraph)** - Query your data in [Memgraph](https://memgraph.com/) graph database.
|
||||
- <img height="12" width="12" src="https://www.mercadolibre.com.ar/favicon.ico" alt="MercadoLibre Logo" /> **[Mercado Libre](https://mcp.mercadolibre.com/)** - Mercado Libre's official MCP server.
|
||||
- <img height="12" width="12" src="https://www.mercadopago.com/favicon.ico" alt="MercadoPago Logo" /> **[Mercado Pago](https://mcp.mercadopago.com/)** - Mercado Pago's official MCP server.
|
||||
@@ -408,7 +411,7 @@ A growing set of community-developed and maintained servers demonstrates various
|
||||
- **[Ableton Live](https://github.com/ahujasid/ableton-mcp)** (by ahujasid) - Ableton integration allowing prompt enabled music creation.
|
||||
- **[Actor Critic Thinking](https://github.com/aquarius-wing/actor-critic-thinking-mcp)** - Actor-critic thinking for performance evaluation
|
||||
- **[AgentBay](https://github.com/Michael98671/agentbay)** - An MCP server for providing serverless cloud infrastructure for AI agents.
|
||||
- **[AgentMode](https://www.agentmode.app) - Connect to dozens of databases, data warehouses, Github & more, from a single MCP server. Run the Docker image locally, in the cloud, or on-premise.
|
||||
- **[AgentMode](https://www.agentmode.app)** - Connect to dozens of databases, data warehouses, Github & more, from a single MCP server. Run the Docker image locally, in the cloud, or on-premise.
|
||||
- **[AI Agent Marketplace Index](https://github.com/AI-Agent-Hub/ai-agent-marketplace-index-mcp)** - MCP server to search more than 5000+ AI agents and tools of various categories from [AI Agent Marketplace Index](http://www.deepnlp.org/store/ai-agent) and monitor traffic of AI Agents.
|
||||
- **[AI Tasks](https://github.com/jbrinkman/valkey-ai-tasks)** - Let the AI manage complex plans with integrated task management and tracking tools. Supports STDIO, SSE and Streamable HTTP transports.
|
||||
- **[ai-Bible](https://github.com/AdbC99/ai-bible)** - Search the bible reliably and repeatably [ai-Bible Labs](https://ai-bible.com)
|
||||
@@ -1064,16 +1067,17 @@ These are high-level frameworks that make it easier to build MCP servers or clie
|
||||
* **[Foxy Contexts](https://github.com/strowk/foxy-contexts)** – A library to build MCP servers in Golang by **[strowk](https://github.com/strowk)**
|
||||
* **[Higress MCP Server Hosting](https://github.com/alibaba/higress/tree/main/plugins/wasm-go/mcp-servers)** - A solution for hosting MCP Servers by extending the API Gateway (based on Envoy) with wasm plugins.
|
||||
* **[MCP Declarative Java SDK](https://github.com/codeboyzhou/mcp-declarative-java-sdk)** Annotation-driven MCP servers development with Java, no Spring Framework Required, minimize dependencies as much as possible.
|
||||
* **[MCP-Framework](https://mcp-framework.com)** Build MCP servers with elegance and speed in Typescript. Comes with a CLI to create your project with `mcp create app`. Get started with your first server in under 5 minutes by **[Alex Andru](https://github.com/QuantGeekDev)**
|
||||
* **[MCP-Framework](https://mcp-framework.com)** Build MCP servers with elegance and speed in TypeScript. Comes with a CLI to create your project with `mcp create app`. Get started with your first server in under 5 minutes by **[Alex Andru](https://github.com/QuantGeekDev)**
|
||||
* **[MCP Plexus](https://github.com/Super-I-Tech/mcp_plexus)**: A secure, **multi-tenant** and Multi-user MCP python server framework built to integrate easily with external services via OAuth 2.1, offering scalable and robust solutions for managing complex AI applications.
|
||||
* **[mcp_sse (Elixir)](https://github.com/kEND/mcp_sse)** An SSE implementation in Elixir for rapidly creating MCP servers.
|
||||
* **[Next.js MCP Server Template](https://github.com/vercel-labs/mcp-for-next.js)** (Typescript) - A starter Next.js project that uses the MCP Adapter to allow MCP clients to connect and access resources.
|
||||
* **[Next.js MCP Server Template](https://github.com/vercel-labs/mcp-for-next.js)** (TypeScript) - A starter Next.js project that uses the MCP Adapter to allow MCP clients to connect and access resources.
|
||||
* **[Quarkus MCP Server SDK](https://github.com/quarkiverse/quarkus-mcp-server)** (Java)
|
||||
- **[R mcptools](https://github.com/posit-dev/mcptools)** - An R SDK for creating R-based MCP servers and retrieving functionality from third-party MCP servers as R functions.
|
||||
* **[SAP ABAP MCP Server SDK](https://github.com/abap-ai/mcp)** - Build SAP ABAP based MCP servers. ABAP 7.52 based with 7.02 downport; runs on R/3 & S/4HANA on-premises, currently not cloud-ready.
|
||||
* **[Spring AI MCP Server](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html)** - Provides auto-configuration for setting up an MCP server in Spring Boot applications.
|
||||
* **[Template MCP Server](https://github.com/mcpdotdirect/template-mcp-server)** - A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
|
||||
* **[AgentR Universal MCP SDK](https://github.com/universal-mcp/universal-mcp)** - A python SDK to build MCP Servers with inbuilt credential management by **[Agentr](https://agentr.dev/home)**
|
||||
* **[Vercel MCP Adapter](https://github.com/vercel/mcp-adapter)** (Typescript) - A simple package to start serving an MCP server on most major JS meta-frameworks including Next, Nuxt, Svelte, and more.
|
||||
* **[Vercel MCP Adapter](https://github.com/vercel/mcp-adapter)** (TypeScript) - A simple package to start serving an MCP server on most major JS meta-frameworks including Next, Nuxt, Svelte, and more.
|
||||
* **[Hermes MCP](https://github.com/cloudwalk/hermes-mcp)** (Elixir) - A high-performance and high-level Model Context Protocol (MCP) implementation in Elixir. Think like "Live View" for MCP.
|
||||
|
||||
|
||||
@@ -1091,6 +1095,7 @@ These are high-level frameworks that make it easier to build MCP servers or clie
|
||||
|
||||
Additional resources on MCP.
|
||||
|
||||
- **[A2A-MCP Java Bridge](https://github.com/vishalmysore/a2ajava)** - A2AJava brings powerful A2A-MCP integration directly into your Java applications. It enables developers to annotate standard Java methods and instantly expose them as MCP Server, A2A-discoverable actions — with no boilerplate or service registration overhead.
|
||||
- **[AiMCP](https://www.aimcp.info)** - A collection of MCP clients&servers to find the right mcp tools by **[Hekmon](https://github.com/hekmon8)**
|
||||
- **[Awesome Crypto MCP Servers by badkk](https://github.com/badkk/awesome-crypto-mcp-servers)** - A curated list of MCP servers by **[Luke Fan](https://github.com/badkk)**
|
||||
- **[Awesome MCP Servers by appcypher](https://github.com/appcypher/awesome-mcp-servers)** - A curated list of MCP servers by **[Stephen Akinyemi](https://github.com/appcypher)**
|
||||
@@ -1109,7 +1114,7 @@ Additional resources on MCP.
|
||||
- **[MCP Linker](https://github.com/milisp/mcp-linker)** - A cross-platform Tauri GUI tool for one-click setup and management of MCP servers, supporting Claude Desktop, Cursor, Windsurf, VS Code, Cline, and Neovim.
|
||||
- **[mcp-manager](https://github.com/zueai/mcp-manager)** - Simple Web UI to install and manage MCP servers for Claude Desktop by **[Zue](https://github.com/zueai)**
|
||||
- **[MCP Marketplace Web Plugin](https://github.com/AI-Agent-Hub/mcp-marketplace)** MCP Marketplace is a small Web UX plugin to integrate with AI applications, Support various MCP Server API Endpoint (e.g pulsemcp.com/deepnlp.org and more). Allowing user to browse, paginate and select various MCP servers by different categories. [Pypi](https://pypi.org/project/mcp-marketplace) | [Maintainer](https://github.com/AI-Agent-Hub) | [Website](http://www.deepnlp.org/store/ai-agent/mcp-server)
|
||||
- **[mcp.natoma.id](https://mcp.natoma.id)** – A Hosted MCP Platform to discover, install, manage and deploy MCP servers by **[Natoma Labs](https://www.natoma.id)**
|
||||
- **[mcp.natoma.ai](https://mcp.natoma.ai)** – A Hosted MCP Platform to discover, install, manage and deploy MCP servers by **[Natoma Labs](https://www.natoma.ai)**
|
||||
- **[mcp.run](https://mcp.run)** - A hosted registry and control plane to install & run secure + portable MCP Servers.
|
||||
- **[MCPHub](https://www.mcphub.com)** - Website to list high quality MCP servers and reviews by real users. Also provide online chatbot for popular LLM models with MCP server support.
|
||||
- **[MCP Router](https://mcp-router.net)** – Free Windows and macOS app that simplifies MCP management while providing seamless app authentication and powerful log visualization by **[MCP Router](https://github.com/mcp-router/mcp-router)**
|
||||
@@ -1138,7 +1143,7 @@ Additional resources on MCP.
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Using MCP Servers in this Repository
|
||||
Typescript-based servers in this repository can be used directly with `npx`.
|
||||
TypeScript-based servers in this repository can be used directly with `npx`.
|
||||
|
||||
For example, this will start the [Memory](src/memory) server:
|
||||
```sh
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -6159,7 +6159,7 @@
|
||||
"version": "0.6.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.12.3",
|
||||
"@modelcontextprotocol/sdk": "^1.17.0",
|
||||
"diff": "^5.1.0",
|
||||
"glob": "^10.3.10",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -6182,9 +6182,9 @@
|
||||
}
|
||||
},
|
||||
"src/filesystem/node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.3.tgz",
|
||||
"integrity": "sha512-DyVYSOafBvk3/j1Oka4z5BWT8o4AFmoNyZY9pALOm7Lh3GZglR71Co4r4dEUoqDWdDazIZQHBe7J2Nwkg6gHgQ==",
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.0.tgz",
|
||||
"integrity": "sha512-qFfbWFA7r1Sd8D697L7GkTd36yqDuTkvz0KfOGkgXR8EUhQn3/EDNIR/qUdQNMT8IjmasBvHWuXeisxtXTQT2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.6",
|
||||
@@ -6192,6 +6192,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"cross-spawn": "^7.0.5",
|
||||
"eventsource": "^3.0.2",
|
||||
"eventsource-parser": "^3.0.0",
|
||||
"express": "^5.0.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"pkce-challenge": "^5.0.0",
|
||||
|
||||
@@ -27,24 +27,24 @@ This MCP server attempts to exercise all the features of the MCP protocol. It is
|
||||
- Returns: Completion message with duration and steps
|
||||
- Sends progress notifications during execution
|
||||
|
||||
4. `sampleLLM`
|
||||
4. `printEnv`
|
||||
- Prints all environment variables
|
||||
- Useful for debugging MCP server configuration
|
||||
- No inputs required
|
||||
- Returns: JSON string of all environment variables
|
||||
|
||||
5. `sampleLLM`
|
||||
- Demonstrates LLM sampling capability using MCP sampling feature
|
||||
- Inputs:
|
||||
- `prompt` (string): The prompt to send to the LLM
|
||||
- `maxTokens` (number, default: 100): Maximum tokens to generate
|
||||
- Returns: Generated LLM response
|
||||
|
||||
5. `getTinyImage`
|
||||
6. `getTinyImage`
|
||||
- Returns a small test image
|
||||
- No inputs required
|
||||
- Returns: Base64 encoded PNG image data
|
||||
|
||||
6. `printEnv`
|
||||
- Prints all environment variables
|
||||
- Useful for debugging MCP server configuration
|
||||
- No inputs required
|
||||
- Returns: JSON string of all environment variables
|
||||
|
||||
7. `annotatedMessage`
|
||||
- Demonstrates how annotations can be used to provide metadata about content
|
||||
- Inputs:
|
||||
@@ -80,6 +80,15 @@ This MCP server attempts to exercise all the features of the MCP protocol. It is
|
||||
- `pets` (enum): Favorite pet
|
||||
- Returns: Confirmation of the elicitation demo with selection summary.
|
||||
|
||||
10. `structuredContent`
|
||||
- Demonstrates a tool returning structured content using the example in the specification
|
||||
- Provides an output schema to allow testing of client SHOULD advisory to validate the result using the schema
|
||||
- Inputs:
|
||||
- `location` (string): A location or ZIP code, mock data is returned regardless of value
|
||||
- Returns: a response with
|
||||
- `structuredContent` field conformant to the output schema
|
||||
- A backward compatible Text Content field, a SHOULD advisory in the specification
|
||||
|
||||
### Resources
|
||||
|
||||
The server provides 100 test resources in two formats:
|
||||
|
||||
@@ -31,6 +31,9 @@ const instructions = readFileSync(join(__dirname, "instructions.md"), "utf-8");
|
||||
const ToolInputSchema = ToolSchema.shape.inputSchema;
|
||||
type ToolInput = z.infer<typeof ToolInputSchema>;
|
||||
|
||||
const ToolOutputSchema = ToolSchema.shape.outputSchema;
|
||||
type ToolOutput = z.infer<typeof ToolOutputSchema>;
|
||||
|
||||
/* Input schemas for tools implemented in this server */
|
||||
const EchoSchema = z.object({
|
||||
message: z.string().describe("Message to echo"),
|
||||
@@ -46,7 +49,10 @@ const LongRunningOperationSchema = z.object({
|
||||
.number()
|
||||
.default(10)
|
||||
.describe("Duration of the operation in seconds"),
|
||||
steps: z.number().default(5).describe("Number of steps in the operation"),
|
||||
steps: z
|
||||
.number()
|
||||
.default(5)
|
||||
.describe("Number of steps in the operation"),
|
||||
});
|
||||
|
||||
const PrintEnvSchema = z.object({});
|
||||
@@ -59,13 +65,6 @@ const SampleLLMSchema = z.object({
|
||||
.describe("Maximum number of tokens to generate"),
|
||||
});
|
||||
|
||||
// Example completion values
|
||||
const EXAMPLE_COMPLETIONS = {
|
||||
style: ["casual", "formal", "technical", "friendly"],
|
||||
temperature: ["0", "0.5", "0.7", "1.0"],
|
||||
resourceId: ["1", "2", "3", "4", "5"],
|
||||
};
|
||||
|
||||
const GetTinyImageSchema = z.object({});
|
||||
|
||||
const AnnotatedMessageSchema = z.object({
|
||||
@@ -97,6 +96,28 @@ const GetResourceLinksSchema = z.object({
|
||||
.describe("Number of resource links to return (1-10)"),
|
||||
});
|
||||
|
||||
const StructuredContentSchema = {
|
||||
input: z.object({
|
||||
location: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.describe("City name or zip code"),
|
||||
}),
|
||||
|
||||
output: z.object({
|
||||
temperature: z
|
||||
.number()
|
||||
.describe("Temperature in celsius"),
|
||||
conditions: z
|
||||
.string()
|
||||
.describe("Weather conditions description"),
|
||||
humidity: z
|
||||
.number()
|
||||
.describe("Humidity percentage"),
|
||||
})
|
||||
};
|
||||
|
||||
enum ToolName {
|
||||
ECHO = "echo",
|
||||
ADD = "add",
|
||||
@@ -108,6 +129,7 @@ enum ToolName {
|
||||
GET_RESOURCE_REFERENCE = "getResourceReference",
|
||||
ELICITATION = "startElicitation",
|
||||
GET_RESOURCE_LINKS = "getResourceLinks",
|
||||
STRUCTURED_CONTENT = "structuredContent"
|
||||
}
|
||||
|
||||
enum PromptName {
|
||||
@@ -116,10 +138,18 @@ enum PromptName {
|
||||
RESOURCE = "resource_prompt",
|
||||
}
|
||||
|
||||
// Example completion values
|
||||
const EXAMPLE_COMPLETIONS = {
|
||||
style: ["casual", "formal", "technical", "friendly"],
|
||||
temperature: ["0", "0.5", "0.7", "1.0"],
|
||||
resourceId: ["1", "2", "3", "4", "5"],
|
||||
};
|
||||
|
||||
export const createServer = () => {
|
||||
const server = new Server(
|
||||
{
|
||||
name: "example-servers/everything",
|
||||
title: "Everything Example Server",
|
||||
version: "1.0.0",
|
||||
},
|
||||
{
|
||||
@@ -179,18 +209,6 @@ export const createServer = () => {
|
||||
}, 20000);
|
||||
|
||||
|
||||
// Set up update interval for stderr messages
|
||||
stdErrUpdateInterval = setInterval(() => {
|
||||
const shortTimestamp = new Date().toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit"
|
||||
});
|
||||
server.notification({
|
||||
method: "notifications/stderr",
|
||||
params: { content: `${shortTimestamp}: A stderr message` },
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
// Helper method to request sampling from client
|
||||
const requestSampling = async (
|
||||
@@ -454,18 +472,18 @@ export const createServer = () => {
|
||||
description: "Adds two numbers",
|
||||
inputSchema: zodToJsonSchema(AddSchema) as ToolInput,
|
||||
},
|
||||
{
|
||||
name: ToolName.PRINT_ENV,
|
||||
description:
|
||||
"Prints all environment variables, helpful for debugging MCP server configuration",
|
||||
inputSchema: zodToJsonSchema(PrintEnvSchema) as ToolInput,
|
||||
},
|
||||
{
|
||||
name: ToolName.LONG_RUNNING_OPERATION,
|
||||
description:
|
||||
"Demonstrates a long running operation with progress updates",
|
||||
inputSchema: zodToJsonSchema(LongRunningOperationSchema) as ToolInput,
|
||||
},
|
||||
{
|
||||
name: ToolName.PRINT_ENV,
|
||||
description:
|
||||
"Prints all environment variables, helpful for debugging MCP server configuration",
|
||||
inputSchema: zodToJsonSchema(PrintEnvSchema) as ToolInput,
|
||||
},
|
||||
{
|
||||
name: ToolName.SAMPLE_LLM,
|
||||
description: "Samples from an LLM using MCP's sampling feature",
|
||||
@@ -499,6 +517,13 @@ export const createServer = () => {
|
||||
"Returns multiple resource links that reference different types of resources",
|
||||
inputSchema: zodToJsonSchema(GetResourceLinksSchema) as ToolInput,
|
||||
},
|
||||
{
|
||||
name: ToolName.STRUCTURED_CONTENT,
|
||||
description:
|
||||
"Returns structured content along with an output schema for client data validation",
|
||||
inputSchema: zodToJsonSchema(StructuredContentSchema.input) as ToolInput,
|
||||
outputSchema: zodToJsonSchema(StructuredContentSchema.output) as ToolOutput,
|
||||
},
|
||||
];
|
||||
|
||||
return { tools };
|
||||
@@ -608,35 +633,6 @@ export const createServer = () => {
|
||||
};
|
||||
}
|
||||
|
||||
if (name === ToolName.GET_RESOURCE_REFERENCE) {
|
||||
const validatedArgs = GetResourceReferenceSchema.parse(args);
|
||||
const resourceId = validatedArgs.resourceId;
|
||||
|
||||
const resourceIndex = resourceId - 1;
|
||||
if (resourceIndex < 0 || resourceIndex >= ALL_RESOURCES.length) {
|
||||
throw new Error(`Resource with ID ${resourceId} does not exist`);
|
||||
}
|
||||
|
||||
const resource = ALL_RESOURCES[resourceIndex];
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Returning resource reference for Resource ${resourceId}:`,
|
||||
},
|
||||
{
|
||||
type: "resource",
|
||||
resource: resource,
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `You can access this resource using the URI: ${resource.uri}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (name === ToolName.ANNOTATED_MESSAGE) {
|
||||
const { messageType, includeImage } = AnnotatedMessageSchema.parse(args);
|
||||
|
||||
@@ -688,6 +684,35 @@ export const createServer = () => {
|
||||
return { content };
|
||||
}
|
||||
|
||||
if (name === ToolName.GET_RESOURCE_REFERENCE) {
|
||||
const validatedArgs = GetResourceReferenceSchema.parse(args);
|
||||
const resourceId = validatedArgs.resourceId;
|
||||
|
||||
const resourceIndex = resourceId - 1;
|
||||
if (resourceIndex < 0 || resourceIndex >= ALL_RESOURCES.length) {
|
||||
throw new Error(`Resource with ID ${resourceId} does not exist`);
|
||||
}
|
||||
|
||||
const resource = ALL_RESOURCES[resourceIndex];
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Returning resource reference for Resource ${resourceId}:`,
|
||||
},
|
||||
{
|
||||
type: "resource",
|
||||
resource: resource,
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `You can access this resource using the URI: ${resource.uri}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (name === ToolName.ELICITATION) {
|
||||
ElicitationSchema.parse(args);
|
||||
|
||||
@@ -709,13 +734,13 @@ export const createServer = () => {
|
||||
|
||||
// Handle different response actions
|
||||
const content = [];
|
||||
|
||||
|
||||
if (elicitationResult.action === 'accept' && elicitationResult.content) {
|
||||
content.push({
|
||||
type: "text",
|
||||
text: `✅ User provided their favorite things!`,
|
||||
});
|
||||
|
||||
|
||||
// Only access elicitationResult.content when action is accept
|
||||
const { color, number, pets } = elicitationResult.content;
|
||||
content.push({
|
||||
@@ -733,7 +758,7 @@ export const createServer = () => {
|
||||
text: `⚠️ User cancelled the elicitation dialog.`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Include raw result for debugging
|
||||
content.push({
|
||||
type: "text",
|
||||
@@ -742,7 +767,7 @@ export const createServer = () => {
|
||||
|
||||
return { content };
|
||||
}
|
||||
|
||||
|
||||
if (name === ToolName.GET_RESOURCE_LINKS) {
|
||||
const { count } = GetResourceLinksSchema.parse(args);
|
||||
const content = [];
|
||||
@@ -773,6 +798,27 @@ export const createServer = () => {
|
||||
return { content };
|
||||
}
|
||||
|
||||
if (name === ToolName.STRUCTURED_CONTENT) {
|
||||
// The same response is returned for every input.
|
||||
const validatedArgs = StructuredContentSchema.input.parse(args);
|
||||
|
||||
const weather = {
|
||||
temperature: 22.5,
|
||||
conditions: "Partly cloudy",
|
||||
humidity: 65
|
||||
}
|
||||
|
||||
const backwardCompatiblecontent = {
|
||||
type: "text",
|
||||
text: JSON.stringify(weather)
|
||||
}
|
||||
|
||||
return {
|
||||
content: [ backwardCompatiblecontent ],
|
||||
structuredContent: weather
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
Testing and demonstration server for MCP protocol features.
|
||||
|
||||
Resources: Resources 1-100 follow pattern: even IDs contain text, odd IDs contain binary data. Resources paginated at 10 items per page with cursor-based navigation.
|
||||
## Resources
|
||||
|
||||
Key dependencies: Progress notifications require `_meta.progressToken` in tool calls. Resource subscriptions generate updates every 10 seconds.
|
||||
Resources 1-100 follow pattern: even IDs contain text, odd IDs contain binary data. Resources are paginated at 10 items per page with cursor-based navigation.
|
||||
|
||||
Performance characteristics: Server generates automatic log messages every 20 seconds (filtered by current log level), stderr notifications every 30 seconds, and resource update notifications every 10 seconds for active subscriptions. Log level changes affect message filtering in real-time.
|
||||
## Key dependencies
|
||||
|
||||
Multi-modal testing: `complex_prompt` includes both text arguments and image content for testing client multi-modal handling. `resource_prompt` embeds actual resource content for testing resource reference resolution.
|
||||
Progress notifications require `_meta.progressToken` in tool calls. Resource subscriptions generate updates every 10 seconds.
|
||||
|
||||
Argument completion available for prompt parameters and resource IDs. Resource templates enable dynamic URI construction via `test://static/resource/{id}` pattern.
|
||||
## Performance characteristics
|
||||
|
||||
Easter egg: If asked about server instructions, respond with "🎉 Server instructions are working! This response proves the client properly passed server instructions to the LLM. This demonstrates MCP's instructions feature in action."
|
||||
Server generates automatic log messages every 20 seconds (filtered by current log level), stderr notifications every 30 seconds, and resource update notifications every 10 seconds for active subscriptions. Log level changes affect message filtering in real-time.
|
||||
|
||||
## Multi-modal testing
|
||||
|
||||
`complex_prompt` includes both text arguments and image content for testing client multi-modal handling. `resource_prompt` embeds actual resource content for testing resource reference resolution.
|
||||
|
||||
Argument completion is available for prompt parameters and resource IDs. Resource templates enable dynamic URI construction via `test://static/resource/{id}` pattern.
|
||||
|
||||
## Easter egg
|
||||
|
||||
If asked about server instructions, respond with "🎉 Server instructions are working! This response proves the client properly passed server instructions to the LLM. This demonstrates MCP's instructions feature in action."
|
||||
|
||||
@@ -9,11 +9,11 @@ Node.js server implementing Model Context Protocol (MCP) for filesystem operatio
|
||||
- Move files/directories
|
||||
- Search files
|
||||
- Get file metadata
|
||||
- Dynamic directory access control via [Roots](https://modelcontextprotocol.io/docs/concepts/roots)
|
||||
- Dynamic directory access control via [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots)
|
||||
|
||||
## Directory Access Control
|
||||
|
||||
The server uses a flexible directory access control system. Directories can be specified via command-line arguments or dynamically via [Roots](https://modelcontextprotocol.io/docs/concepts/roots).
|
||||
The server uses a flexible directory access control system. Directories can be specified via command-line arguments or dynamically via [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots).
|
||||
|
||||
### Method 1: Command-line Arguments
|
||||
Specify Allowed directories when starting the server:
|
||||
@@ -22,7 +22,7 @@ mcp-server-filesystem /path/to/dir1 /path/to/dir2
|
||||
```
|
||||
|
||||
### Method 2: MCP Roots (Recommended)
|
||||
MCP clients that support [Roots](https://modelcontextprotocol.io/docs/concepts/roots) can dynamically update the Allowed directories.
|
||||
MCP clients that support [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) can dynamically update the Allowed directories.
|
||||
|
||||
Roots notified by Client to Server, completely replace any server-side Allowed directories when provided.
|
||||
|
||||
@@ -70,10 +70,19 @@ The server's directory access control follows this flow:
|
||||
|
||||
### Tools
|
||||
|
||||
- **read_file**
|
||||
- Read complete contents of a file
|
||||
- Input: `path` (string)
|
||||
- Reads complete file contents with UTF-8 encoding
|
||||
- **read_text_file**
|
||||
- Read complete contents of a file as text
|
||||
- Inputs:
|
||||
- `path` (string)
|
||||
- `head` (number, optional): First N lines
|
||||
- `tail` (number, optional): Last N lines
|
||||
- Always treats the file as UTF-8 text regardless of extension
|
||||
|
||||
- **read_media_file**
|
||||
- Read an image or audio file
|
||||
- Inputs:
|
||||
- `path` (string)
|
||||
- Streams the file and returns base64 data with the corresponding MIME type
|
||||
|
||||
- **read_multiple_files**
|
||||
- Read multiple files simultaneously
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type Root,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import fs from "fs/promises";
|
||||
import { createReadStream } from "fs";
|
||||
import path from "path";
|
||||
import os from 'os';
|
||||
import { randomBytes } from 'crypto';
|
||||
@@ -116,12 +117,16 @@ async function validatePath(requestedPath: string): Promise<string> {
|
||||
}
|
||||
|
||||
// Schema definitions
|
||||
const ReadFileArgsSchema = z.object({
|
||||
const ReadTextFileArgsSchema = z.object({
|
||||
path: z.string(),
|
||||
tail: z.number().optional().describe('If provided, returns only the last N lines of the file'),
|
||||
head: z.number().optional().describe('If provided, returns only the first N lines of the file')
|
||||
});
|
||||
|
||||
const ReadMediaFileArgsSchema = z.object({
|
||||
path: z.string()
|
||||
});
|
||||
|
||||
const ReadMultipleFilesArgsSchema = z.object({
|
||||
paths: z.array(z.string()),
|
||||
});
|
||||
@@ -374,10 +379,10 @@ async function applyFileEdits(
|
||||
function formatSize(bytes: number): string {
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
if (i === 0) return `${bytes} ${units[i]}`;
|
||||
|
||||
|
||||
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
|
||||
}
|
||||
|
||||
@@ -386,9 +391,9 @@ async function tailFile(filePath: string, numLines: number): Promise<string> {
|
||||
const CHUNK_SIZE = 1024; // Read 1KB at a time
|
||||
const stats = await fs.stat(filePath);
|
||||
const fileSize = stats.size;
|
||||
|
||||
|
||||
if (fileSize === 0) return '';
|
||||
|
||||
|
||||
// Open file for reading
|
||||
const fileHandle = await fs.open(filePath, 'r');
|
||||
try {
|
||||
@@ -397,36 +402,36 @@ async function tailFile(filePath: string, numLines: number): Promise<string> {
|
||||
let chunk = Buffer.alloc(CHUNK_SIZE);
|
||||
let linesFound = 0;
|
||||
let remainingText = '';
|
||||
|
||||
|
||||
// Read chunks from the end of the file until we have enough lines
|
||||
while (position > 0 && linesFound < numLines) {
|
||||
const size = Math.min(CHUNK_SIZE, position);
|
||||
position -= size;
|
||||
|
||||
|
||||
const { bytesRead } = await fileHandle.read(chunk, 0, size, position);
|
||||
if (!bytesRead) break;
|
||||
|
||||
|
||||
// Get the chunk as a string and prepend any remaining text from previous iteration
|
||||
const readData = chunk.slice(0, bytesRead).toString('utf-8');
|
||||
const chunkText = readData + remainingText;
|
||||
|
||||
|
||||
// Split by newlines and count
|
||||
const chunkLines = normalizeLineEndings(chunkText).split('\n');
|
||||
|
||||
|
||||
// If this isn't the end of the file, the first line is likely incomplete
|
||||
// Save it to prepend to the next chunk
|
||||
if (position > 0) {
|
||||
remainingText = chunkLines[0];
|
||||
chunkLines.shift(); // Remove the first (incomplete) line
|
||||
}
|
||||
|
||||
|
||||
// Add lines to our result (up to the number we need)
|
||||
for (let i = chunkLines.length - 1; i >= 0 && linesFound < numLines; i--) {
|
||||
lines.unshift(chunkLines[i]);
|
||||
linesFound++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return lines.join('\n');
|
||||
} finally {
|
||||
await fileHandle.close();
|
||||
@@ -441,14 +446,14 @@ async function headFile(filePath: string, numLines: number): Promise<string> {
|
||||
let buffer = '';
|
||||
let bytesRead = 0;
|
||||
const chunk = Buffer.alloc(1024); // 1KB buffer
|
||||
|
||||
|
||||
// Read chunks and count lines until we have enough or reach EOF
|
||||
while (lines.length < numLines) {
|
||||
const result = await fileHandle.read(chunk, 0, chunk.length, bytesRead);
|
||||
if (result.bytesRead === 0) break; // End of file
|
||||
bytesRead += result.bytesRead;
|
||||
buffer += chunk.slice(0, result.bytesRead).toString('utf-8');
|
||||
|
||||
|
||||
const newLineIndex = buffer.lastIndexOf('\n');
|
||||
if (newLineIndex !== -1) {
|
||||
const completeLines = buffer.slice(0, newLineIndex).split('\n');
|
||||
@@ -459,32 +464,63 @@ async function headFile(filePath: string, numLines: number): Promise<string> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If there is leftover content and we still need lines, add it
|
||||
if (buffer.length > 0 && lines.length < numLines) {
|
||||
lines.push(buffer);
|
||||
}
|
||||
|
||||
|
||||
return lines.join('\n');
|
||||
} finally {
|
||||
await fileHandle.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Reads a file as a stream of buffers, concatenates them, and then encodes
|
||||
// the result to a Base64 string. This is a memory-efficient way to handle
|
||||
// binary data from a stream before the final encoding.
|
||||
async function readFileAsBase64Stream(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const stream = createReadStream(filePath);
|
||||
const chunks: Buffer[] = [];
|
||||
stream.on('data', (chunk) => {
|
||||
chunks.push(chunk as Buffer);
|
||||
});
|
||||
stream.on('end', () => {
|
||||
const finalBuffer = Buffer.concat(chunks);
|
||||
resolve(finalBuffer.toString('base64'));
|
||||
});
|
||||
stream.on('error', (err) => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
// Tool handlers
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "read_file",
|
||||
description: "Read the complete contents of a file as text. DEPRECATED: Use read_text_file instead.",
|
||||
inputSchema: zodToJsonSchema(ReadTextFileArgsSchema) as ToolInput,
|
||||
},
|
||||
{
|
||||
name: "read_text_file",
|
||||
description:
|
||||
"Read the complete contents of a file from the file system. " +
|
||||
"Read the complete contents of a file from the file system as text. " +
|
||||
"Handles various text encodings and provides detailed error messages " +
|
||||
"if the file cannot be read. Use this tool when you need to examine " +
|
||||
"the contents of a single file. Use the 'head' parameter to read only " +
|
||||
"the first N lines of a file, or the 'tail' parameter to read only " +
|
||||
"the last N lines of a file. Only works within allowed directories.",
|
||||
inputSchema: zodToJsonSchema(ReadFileArgsSchema) as ToolInput,
|
||||
"the last N lines of a file. Operates on the file as text regardless of extension. " +
|
||||
"Only works within allowed directories.",
|
||||
inputSchema: zodToJsonSchema(ReadTextFileArgsSchema) as ToolInput,
|
||||
},
|
||||
{
|
||||
name: "read_media_file",
|
||||
description:
|
||||
"Read an image or audio file. Returns the base64 encoded data and MIME type. " +
|
||||
"Only works within allowed directories.",
|
||||
inputSchema: zodToJsonSchema(ReadMediaFileArgsSchema) as ToolInput,
|
||||
},
|
||||
{
|
||||
name: "read_multiple_files",
|
||||
@@ -597,17 +633,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
switch (name) {
|
||||
case "read_file": {
|
||||
const parsed = ReadFileArgsSchema.safeParse(args);
|
||||
case "read_file":
|
||||
case "read_text_file": {
|
||||
const parsed = ReadTextFileArgsSchema.safeParse(args);
|
||||
if (!parsed.success) {
|
||||
throw new Error(`Invalid arguments for read_file: ${parsed.error}`);
|
||||
throw new Error(`Invalid arguments for read_text_file: ${parsed.error}`);
|
||||
}
|
||||
const validPath = await validatePath(parsed.data.path);
|
||||
|
||||
|
||||
if (parsed.data.head && parsed.data.tail) {
|
||||
throw new Error("Cannot specify both head and tail parameters simultaneously");
|
||||
}
|
||||
|
||||
|
||||
if (parsed.data.tail) {
|
||||
// Use memory-efficient tail implementation for large files
|
||||
const tailContent = await tailFile(validPath, parsed.data.tail);
|
||||
@@ -615,7 +652,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
content: [{ type: "text", text: tailContent }],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (parsed.data.head) {
|
||||
// Use memory-efficient head implementation for large files
|
||||
const headContent = await headFile(validPath, parsed.data.head);
|
||||
@@ -623,13 +660,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
content: [{ type: "text", text: headContent }],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const content = await fs.readFile(validPath, "utf-8");
|
||||
return {
|
||||
content: [{ type: "text", text: content }],
|
||||
};
|
||||
}
|
||||
|
||||
case "read_media_file": {
|
||||
const parsed = ReadMediaFileArgsSchema.safeParse(args);
|
||||
if (!parsed.success) {
|
||||
throw new Error(`Invalid arguments for read_media_file: ${parsed.error}`);
|
||||
}
|
||||
const validPath = await validatePath(parsed.data.path);
|
||||
const extension = path.extname(validPath).toLowerCase();
|
||||
const mimeTypes: Record<string, string> = {
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
".bmp": "image/bmp",
|
||||
".svg": "image/svg+xml",
|
||||
".mp3": "audio/mpeg",
|
||||
".wav": "audio/wav",
|
||||
".ogg": "audio/ogg",
|
||||
".flac": "audio/flac",
|
||||
};
|
||||
const mimeType = mimeTypes[extension] || "application/octet-stream";
|
||||
const data = await readFileAsBase64Stream(validPath);
|
||||
const type = mimeType.startsWith("image/")
|
||||
? "image"
|
||||
: mimeType.startsWith("audio/")
|
||||
? "audio"
|
||||
: "blob";
|
||||
return {
|
||||
content: [{ type, data, mimeType }],
|
||||
};
|
||||
}
|
||||
|
||||
case "read_multiple_files": {
|
||||
const parsed = ReadMultipleFilesArgsSchema.safeParse(args);
|
||||
if (!parsed.success) {
|
||||
@@ -734,7 +803,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
}
|
||||
const validPath = await validatePath(parsed.data.path);
|
||||
const entries = await fs.readdir(validPath, { withFileTypes: true });
|
||||
|
||||
|
||||
// Get detailed information for each entry
|
||||
const detailedEntries = await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
@@ -757,7 +826,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
// Sort entries based on sortBy parameter
|
||||
const sortedEntries = [...detailedEntries].sort((a, b) => {
|
||||
if (parsed.data.sortBy === 'size') {
|
||||
@@ -766,29 +835,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
// Default sort by name
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
|
||||
// Format the output
|
||||
const formattedEntries = sortedEntries.map(entry =>
|
||||
const formattedEntries = sortedEntries.map(entry =>
|
||||
`${entry.isDirectory ? "[DIR]" : "[FILE]"} ${entry.name.padEnd(30)} ${
|
||||
entry.isDirectory ? "" : formatSize(entry.size).padStart(10)
|
||||
}`
|
||||
);
|
||||
|
||||
|
||||
// Add summary
|
||||
const totalFiles = detailedEntries.filter(e => !e.isDirectory).length;
|
||||
const totalDirs = detailedEntries.filter(e => e.isDirectory).length;
|
||||
const totalSize = detailedEntries.reduce((sum, entry) => sum + (entry.isDirectory ? 0 : entry.size), 0);
|
||||
|
||||
|
||||
const summary = [
|
||||
"",
|
||||
`Total: ${totalFiles} files, ${totalDirs} directories`,
|
||||
`Combined size: ${formatSize(totalSize)}`
|
||||
];
|
||||
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: [...formattedEntries, ...summary].join("\n")
|
||||
content: [{
|
||||
type: "text",
|
||||
text: [...formattedEntries, ...summary].join("\n")
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"test": "jest --config=jest.config.cjs --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.12.3",
|
||||
"@modelcontextprotocol/sdk": "^1.17.0",
|
||||
"diff": "^5.1.0",
|
||||
"glob": "^10.3.10",
|
||||
"minimatch": "^10.0.1",
|
||||
@@ -38,4 +38,4 @@
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
from freezegun import freeze_time
|
||||
from mcp.shared.exceptions import McpError
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from mcp_server_time.server import TimeServer
|
||||
from mcp_server_time.server import TimeServer, get_local_tz
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -458,3 +460,69 @@ def test_convert_time(test_time, source_tz, time_str, target_tz, expected):
|
||||
assert result.source.is_dst == expected["source"]["is_dst"]
|
||||
assert result.target.is_dst == expected["target"]["is_dst"]
|
||||
assert result.time_difference == expected["time_difference"]
|
||||
|
||||
|
||||
def test_get_local_tz_with_override():
|
||||
"""Test that timezone override works correctly."""
|
||||
result = get_local_tz("America/New_York")
|
||||
assert str(result) == "America/New_York"
|
||||
assert isinstance(result, ZoneInfo)
|
||||
|
||||
|
||||
def test_get_local_tz_with_invalid_override():
|
||||
"""Test that invalid timezone override raises an error."""
|
||||
with pytest.raises(Exception): # ZoneInfo will raise an exception
|
||||
get_local_tz("Invalid/Timezone")
|
||||
|
||||
|
||||
@patch('mcp_server_time.server.get_localzone_name')
|
||||
def test_get_local_tz_with_valid_iana_name(mock_get_localzone):
|
||||
"""Test that valid IANA timezone names from tzlocal work correctly."""
|
||||
mock_get_localzone.return_value = "Europe/London"
|
||||
result = get_local_tz()
|
||||
assert str(result) == "Europe/London"
|
||||
assert isinstance(result, ZoneInfo)
|
||||
|
||||
|
||||
@patch('mcp_server_time.server.get_localzone_name')
|
||||
def test_get_local_tz_when_none_returned(mock_get_localzone):
|
||||
"""Test error when tzlocal returns None."""
|
||||
mock_get_localzone.return_value = None
|
||||
with pytest.raises(McpError, match="Could not determine local timezone"):
|
||||
get_local_tz()
|
||||
|
||||
|
||||
@patch('mcp_server_time.server.get_localzone_name')
|
||||
def test_get_local_tz_handles_windows_timezones(mock_get_localzone):
|
||||
"""Test that tzlocal properly handles Windows timezone names.
|
||||
|
||||
Note: tzlocal should convert Windows names like 'Pacific Standard Time'
|
||||
to proper IANA names like 'America/Los_Angeles'.
|
||||
"""
|
||||
# tzlocal should return IANA names even on Windows
|
||||
mock_get_localzone.return_value = "America/Los_Angeles"
|
||||
result = get_local_tz()
|
||||
assert str(result) == "America/Los_Angeles"
|
||||
assert isinstance(result, ZoneInfo)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"timezone_name",
|
||||
[
|
||||
"America/New_York",
|
||||
"Europe/Paris",
|
||||
"Asia/Tokyo",
|
||||
"Australia/Sydney",
|
||||
"Africa/Cairo",
|
||||
"America/Sao_Paulo",
|
||||
"Pacific/Auckland",
|
||||
"UTC",
|
||||
],
|
||||
)
|
||||
@patch('mcp_server_time.server.get_localzone_name')
|
||||
def test_get_local_tz_various_timezones(mock_get_localzone, timezone_name):
|
||||
"""Test various timezone names that tzlocal might return."""
|
||||
mock_get_localzone.return_value = timezone_name
|
||||
result = get_local_tz()
|
||||
assert str(result) == timezone_name
|
||||
assert isinstance(result, ZoneInfo)
|
||||
|
||||
Reference in New Issue
Block a user