├── .github ├── resources │ ├── logo.png │ ├── configs.png │ ├── screenshot.png │ └── web-interface.png ├── workflows │ ├── shellcheck.yml │ ├── go-test.yml │ └── go-lint.yml └── pull_request_template.md ├── templates ├── ts │ ├── tsconfig.json │ ├── resource.ts │ ├── package.json │ ├── server_stdio.ts │ ├── prompt.ts │ ├── tool.ts │ ├── server_sse.ts │ └── server_http.ts └── README.md ├── .gitignore ├── examples ├── add.sh └── greet.sh ├── cmd └── mcptools │ ├── commands │ ├── version.go │ ├── tools.go │ ├── version_test.go │ ├── prompts.go │ ├── resources.go │ ├── prompts_test.go │ ├── tools_test.go │ ├── resources_test.go │ ├── read_resource_test.go │ ├── root.go │ ├── read_resource.go │ ├── call_test.go │ ├── get_prompt.go │ ├── test_helpers.go │ ├── alias.go │ ├── mock.go │ ├── alias_test.go │ ├── call.go │ ├── utils_test.go │ ├── guard_test.go │ ├── guard.go │ ├── proxy.go │ ├── utils.go │ ├── new.go │ ├── shell.go │ └── shell_test.go │ ├── main.go │ ├── transport_test.go │ └── main_test.go ├── .golangci.yml ├── .gitmessage ├── LICENSE ├── Makefile ├── go.mod ├── .goreleaser.yml ├── pkg ├── guard │ ├── child_process.go │ └── guard_proxy.go ├── alias │ └── alias.go ├── jsonutils │ └── jsonutils_test.go └── mock │ └── mock.go ├── CONTRIBUTING.md ├── scripts └── check_go.bash └── go.sum /.github/resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/mcptools/HEAD/.github/resources/logo.png -------------------------------------------------------------------------------- /.github/resources/configs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/mcptools/HEAD/.github/resources/configs.png -------------------------------------------------------------------------------- /.github/resources/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/mcptools/HEAD/.github/resources/screenshot.png -------------------------------------------------------------------------------- /.github/resources/web-interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/mcptools/HEAD/.github/resources/web-interface.png -------------------------------------------------------------------------------- /templates/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"] 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/ 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # GoReleaser 16 | dist/ 17 | 18 | # IDE specific files 19 | .idea/ 20 | .vscode/ 21 | *.swp 22 | *.swo 23 | 24 | .DS_Store -------------------------------------------------------------------------------- /examples/add.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the values from environment variables 4 | if [ -z "$a" ] || [ -z "$b" ]; then 5 | echo "Error: Missing required parameters 'a' or 'b'" 6 | exit 1 7 | fi 8 | 9 | # Try to convert to integers 10 | a_val=$(($a)) 11 | b_val=$(($b)) 12 | 13 | # Perform the addition 14 | result=$(($a_val + $b_val)) 15 | 16 | # Return the result 17 | echo "The sum of $a and $b is $result" -------------------------------------------------------------------------------- /templates/ts/resource.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | 3 | // Define a resource 4 | export default (server: McpServer) => { 5 | server.resource( 6 | "RESOURCE_NAME", 7 | "RESOURCE_URI", 8 | async (uri) => ({ 9 | contents: [{ 10 | uri: uri.href, 11 | text: "This is a sample resource content. Replace with your actual content." 12 | }] 13 | }) 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /templates/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PROJECT_NAME", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "build": "tsc", 7 | "start": "node build/index.js" 8 | }, 9 | "dependencies": { 10 | "@modelcontextprotocol/sdk": "^1.1.0", 11 | "zod": "^3.22.4", 12 | "express": "^4.18.2" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^22.10.5", 16 | "@types/express": "^4.17.21", 17 | "typescript": "^5.7.2" 18 | } 19 | } -------------------------------------------------------------------------------- /templates/ts/server_stdio.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { z } from "zod"; 4 | 5 | // Initialize server 6 | const server = new McpServer({ 7 | name: "PROJECT_NAME", 8 | version: "1.0.0" 9 | }); 10 | 11 | // === Start server with stdio transport === 12 | const transport = new StdioServerTransport(); 13 | await server.connect(transport); 14 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | name: Shellcheck 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.bash' 7 | push: 8 | branches: 9 | - main 10 | - master 11 | tags-ignore: 12 | - '**' 13 | paths: 14 | - '**.bash' 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-24.04 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Run shellchecker 24 | run: | 25 | shellcheck --shell=bash scripts/*.bash 26 | -------------------------------------------------------------------------------- /examples/greet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the values from environment variables 4 | if [ -z "$name" ]; then 5 | echo "Error: Missing required parameter 'name'" 6 | exit 1 7 | fi 8 | 9 | # Set default values if not provided 10 | if [ -z "$greeting" ]; then 11 | greeting="Hello" 12 | fi 13 | 14 | if [ -z "$formal" ]; then 15 | formal=false 16 | fi 17 | 18 | # Customize greeting based on formal flag 19 | if [ "$formal" = "true" ]; then 20 | title="Mr./Ms." 21 | message="${greeting}, ${title} ${name}. How may I assist you today?" 22 | else 23 | message="${greeting}, ${name}! Nice to meet you!" 24 | fi 25 | 26 | # Return the greeting 27 | echo "$message" -------------------------------------------------------------------------------- /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: Run go tests 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | push: 8 | branches: 9 | - main 10 | - master 11 | tags-ignore: 12 | - '**' 13 | paths: 14 | - '**.go' 15 | 16 | concurrency: 17 | group: mcp-go-test 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | test: 22 | name: Run tests 23 | runs-on: ubuntu-24.04 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-go@v5 27 | with: 28 | go-version-file: "go.mod" 29 | id: go 30 | 31 | - name: Run tests 32 | run: go test -race -v ./... 33 | -------------------------------------------------------------------------------- /.github/workflows/go-lint.yml: -------------------------------------------------------------------------------- 1 | name: Run golangci-lint 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | push: 8 | branches: 9 | - main 10 | - master 11 | paths: 12 | - '**.go' 13 | 14 | concurrency: 15 | group: golangci-lint-mcp 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | lint: 20 | name: Run golangci-lint 21 | runs-on: ubuntu-24.04 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: actions/setup-go@v5 26 | with: 27 | go-version-file: "go.mod" 28 | 29 | - name: golangci-lint 30 | uses: golangci/golangci-lint-action@v7 31 | with: 32 | version: v2.0 33 | args: --timeout=5m 34 | -------------------------------------------------------------------------------- /templates/ts/prompt.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | 4 | // Define a prompt template 5 | export default (server: McpServer) => { 6 | server.prompt( 7 | "PROMPT_NAME", 8 | { 9 | // Define the parameters for your prompt using Zod 10 | name: z.string({ 11 | description: "The name to use in the greeting" 12 | }), 13 | time_of_day: z.enum(["morning", "afternoon", "evening", "night"], { 14 | description: "The time of day for the greeting" 15 | }) 16 | }, 17 | (params) => ({ 18 | messages: [{ 19 | role: "user", 20 | content: { 21 | type: "text", 22 | text: `Hello ${params.name}! Good ${params.time_of_day}. How are you today?` 23 | } 24 | }] 25 | }) 26 | ); 27 | }; -------------------------------------------------------------------------------- /templates/ts/tool.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | 4 | export default (server: McpServer) => { 5 | // Define a calculator tool 6 | server.tool( 7 | "TOOL_NAME", 8 | "TOOL_DESCRIPTION", 9 | { 10 | // Define the parameters for your tool using Zod 11 | 12 | someEnum: z.enum(["option1", "option2", "option3"], { 13 | description: "An enum parameter" 14 | }), 15 | aNumber: z.number({ 16 | description: "A number parameter" 17 | }), 18 | aString: z.string({ 19 | description: "A string parameter" 20 | }) 21 | }, 22 | async (params) => { 23 | 24 | // Implement the tool logic here 25 | 26 | return { 27 | content: [{ 28 | type: "text", 29 | text: "This is the tool response to the user's request" 30 | }] 31 | }; 32 | } 33 | ); 34 | } -------------------------------------------------------------------------------- /cmd/mcptools/commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // Version information placeholder. 11 | var Version = "dev" 12 | 13 | // getHomeDirectory returns the user's home directory 14 | // Tries HOME first, then falls back to USERPROFILE for Windows. 15 | func getHomeDirectory() string { 16 | homeDir := os.Getenv("HOME") 17 | if homeDir == "" { 18 | homeDir = os.Getenv("USERPROFILE") 19 | } 20 | return homeDir 21 | } 22 | 23 | // TemplatesPath information placeholder. 24 | var TemplatesPath = getHomeDirectory() + "/.mcpt/templates" 25 | 26 | // VersionCmd creates the version command. 27 | func VersionCmd() *cobra.Command { 28 | return &cobra.Command{ 29 | Use: "version", 30 | Short: "Print the version information", 31 | Run: func(cmd *cobra.Command, _ []string) { 32 | fmt.Fprintf(cmd.OutOrStdout(), "MCP Tools version %s\n", Version) 33 | }, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | concurrency: 4 4 | modules-download-mode: readonly 5 | 6 | linters: 7 | enable: 8 | - errorlint 9 | - goconst 10 | - gocritic 11 | - gocyclo 12 | - godot 13 | - godox 14 | - gomoddirectives 15 | - gosec 16 | - makezero 17 | - nilerr 18 | - revive 19 | - unparam 20 | settings: 21 | errcheck: 22 | exclude-functions: 23 | - fmt.Fprintln 24 | - fmt.Fprintf 25 | govet: 26 | enable-all: true 27 | settings: 28 | shadow: 29 | strict: false 30 | exclusions: 31 | generated: lax 32 | paths: 33 | - third_party$ 34 | - builtin$ 35 | - examples$ 36 | 37 | issues: 38 | max-issues-per-linter: 0 39 | max-same-issues: 0 40 | formatters: 41 | enable: 42 | - gofmt 43 | - gofumpt 44 | - goimports 45 | exclusions: 46 | generated: lax 47 | paths: 48 | - third_party$ 49 | - builtin$ 50 | - examples$ 51 | -------------------------------------------------------------------------------- /templates/ts/server_sse.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; 3 | import express from "express"; 4 | import { z } from "zod"; 5 | 6 | // Initialize server 7 | const server = new McpServer({ 8 | name: "PROJECT_NAME", 9 | version: "1.0.0" 10 | }); 11 | 12 | // Setup Express app 13 | const app = express(); 14 | 15 | // Create an SSE endpoint that clients can connect to 16 | app.get("/sse", async (req, res) => { 17 | const transport = new SSEServerTransport("/messages", res); 18 | await server.connect(transport); 19 | }); 20 | 21 | // Create an endpoint to receive messages from clients 22 | app.post("/messages", express.json(), async (req, res) => { 23 | // Handle the message and send response 24 | res.json({ success: true }); 25 | }); 26 | 27 | // Start HTTP server 28 | const port = 3000; 29 | app.listen(port, () => { 30 | console.log(`MCP server running on http://localhost:${port}/sse`); 31 | }); -------------------------------------------------------------------------------- /cmd/mcptools/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package main implements mcp functionality. 3 | */ 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/f/mcptools/cmd/mcptools/commands" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // Build parameters. 14 | var ( 15 | Version string 16 | TemplatesPath string 17 | ) 18 | 19 | func init() { 20 | commands.Version = Version 21 | commands.TemplatesPath = TemplatesPath 22 | } 23 | 24 | func main() { 25 | cobra.EnableCommandSorting = false 26 | 27 | rootCmd := commands.RootCmd() 28 | rootCmd.AddCommand( 29 | commands.VersionCmd(), 30 | commands.ToolsCmd(), 31 | commands.ResourcesCmd(), 32 | commands.PromptsCmd(), 33 | commands.CallCmd(), 34 | commands.GetPromptCmd(), 35 | commands.ReadResourceCmd(), 36 | commands.ShellCmd(), 37 | commands.WebCmd(), 38 | commands.MockCmd(), 39 | commands.ProxyCmd(), 40 | commands.AliasCmd(), 41 | commands.ConfigsCmd(), 42 | commands.NewCmd(), 43 | commands.GuardCmd(), 44 | ) 45 | 46 | if err := rootCmd.Execute(); err != nil { 47 | os.Exit(1) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.gitmessage: -------------------------------------------------------------------------------- 1 | # : 2 | # |<---- Using a maximum of 50 characters ---->| 3 | 4 | # Explain why this change is being made 5 | # |<---- Try to limit each line to a maximum of 72 characters ---->| 6 | 7 | # Provide links to any relevant tickets, issues, or other resources 8 | # Example: Resolves: #123 9 | # See: #456, #789 10 | 11 | # --- COMMIT END --- 12 | # Type can be 13 | # feat (new feature) 14 | # fix (bug fix) 15 | # refactor (refactoring code) 16 | # style (formatting, missing semicolons, etc; no code change) 17 | # docs (changes to documentation) 18 | # test (adding or refactoring tests; no production code change) 19 | # chore (updating grunt tasks etc; no production code change) 20 | # -------------------- 21 | # Remember to 22 | # Use the imperative mood in the subject line 23 | # Do not end the subject line with a period 24 | # Separate subject from body with a blank line 25 | # Use the body to explain what and why vs. how 26 | # Can use multiple lines with "-" for bullet points in body 27 | # -------------------- -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 f 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Color definitions 2 | BLUE=\033[0;34m 3 | GREEN=\033[0;32m 4 | YELLOW=\033[0;33m 5 | RED=\033[0;31m 6 | NC=\033[0m # No Color 7 | 8 | BINARY_NAME=mcp 9 | ALIAS_NAME=mcpt 10 | VERSION=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") 11 | 12 | # Default Makefile step 13 | default: setup 14 | @echo "$(GREEN)All setup steps completed successfully!$(NC)" 15 | 16 | check-go: 17 | @echo "$(BLUE)Checking Go installation and version...$(NC)" 18 | chmod +x ./scripts/check_go.bash 19 | ./scripts/check_go.bash 20 | 21 | build: 22 | @echo "$(YELLOW)Building $(BINARY_NAME)...$(NC)" 23 | go build -ldflags "-X main.Version=$(VERSION) -X main.TemplatesPath=$(HOME)/.mcpt/templates" -o bin/$(BINARY_NAME) ./cmd/mcptools 24 | 25 | install-templates: 26 | mkdir -p $(HOME)/.mcpt/templates 27 | cp -r $(CURDIR)/templates/* $(HOME)/.mcpt/templates/ 28 | 29 | test: check-go 30 | @echo "$(YELLOW)Running tests...$(NC)" 31 | go test -v ./... 32 | 33 | lint: check-go 34 | @echo "$(BLUE)Running linter...$(NC)" 35 | golangci-lint run ./... 36 | 37 | dist: 38 | mkdir -p dist 39 | 40 | clean: 41 | rm -rf bin/* dist/* 42 | 43 | release: clean lint test build 44 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. 4 | 5 | Fixes # (issue) 6 | 7 | ## Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] This change requires a documentation update 15 | 16 | ## How Has This Been Tested? 17 | 18 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. 19 | 20 | ## Checklist: 21 | 22 | - [ ] My code follows the style guidelines of this project 23 | - [ ] I have performed a self-review of my own code 24 | - [ ] I have commented my code, particularly in hard-to-understand areas 25 | - [ ] I have made corresponding changes to the documentation 26 | - [ ] My changes generate no new warnings 27 | - [ ] I have added tests that prove my fix is effective or that my feature works 28 | - [ ] New and existing unit tests pass locally with my changes 29 | - [ ] Any dependent changes have been merged and published in downstream modules -------------------------------------------------------------------------------- /templates/ts/server_http.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; 3 | import express from "express"; 4 | import { z } from "zod"; 5 | import { randomUUID } from "crypto"; 6 | 7 | // Initialize server 8 | const server = new McpServer({ 9 | name: "PROJECT_NAME", 10 | version: "1.0.0" 11 | }); 12 | 13 | // Initialize components here 14 | // COMPONENT_INITIALIZATION 15 | 16 | // Setup Express app 17 | const app = express(); 18 | 19 | // Enable JSON parsing for request bodies 20 | app.use(express.json()); 21 | 22 | // Create a streamable HTTP transport 23 | const transport = new StreamableHTTPServerTransport({ 24 | // Enable session management with auto-generated UUIDs 25 | sessionIdGenerator: () => randomUUID(), 26 | // Optional: Enable JSON response mode for simple request/response 27 | enableJsonResponse: false 28 | }); 29 | 30 | // Handle all HTTP methods on the root path 31 | app.all("/", async (req, res) => { 32 | await transport.handleRequest(req, res, req.body); 33 | }); 34 | 35 | // Connect the server to the transport 36 | await server.connect(transport); 37 | 38 | // Start HTTP server 39 | const port = 3000; 40 | app.listen(port, () => { 41 | console.log(`MCP server running on http://localhost:${port}`); 42 | }); -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/f/mcptools 2 | 3 | go 1.24.1 4 | 5 | require ( 6 | github.com/mark3labs/mcp-go v0.34.0 7 | github.com/peterh/liner v1.2.2 8 | github.com/spf13/cobra v1.9.1 9 | github.com/spf13/viper v1.20.1 10 | github.com/stretchr/testify v1.10.0 11 | golang.org/x/term v0.30.0 12 | golang.org/x/text v0.23.0 13 | ) 14 | 15 | require ( 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/fsnotify/fsnotify v1.8.0 // indirect 18 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 19 | github.com/google/uuid v1.6.0 // indirect 20 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 21 | github.com/mattn/go-runewidth v0.0.16 // indirect 22 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 23 | github.com/pmezard/go-difflib v1.0.0 // indirect 24 | github.com/rivo/uniseg v0.4.7 // indirect 25 | github.com/rogpeppe/go-internal v1.12.0 // indirect 26 | github.com/sagikazarmark/locafero v0.7.0 // indirect 27 | github.com/sourcegraph/conc v0.3.0 // indirect 28 | github.com/spf13/afero v1.12.0 // indirect 29 | github.com/spf13/cast v1.7.1 // indirect 30 | github.com/spf13/pflag v1.0.6 // indirect 31 | github.com/subosito/gotenv v1.6.0 // indirect 32 | github.com/yosida95/uritemplate/v3 v3.0.2 // indirect 33 | go.uber.org/atomic v1.9.0 // indirect 34 | go.uber.org/multierr v1.9.0 // indirect 35 | golang.org/x/sys v0.31.0 // indirect 36 | gopkg.in/yaml.v3 v3.0.1 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/tools.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // ToolsCmd creates the tools command. 13 | func ToolsCmd() *cobra.Command { 14 | return &cobra.Command{ 15 | Use: "tools [command args...]", 16 | Short: "List available tools on the MCP server", 17 | DisableFlagParsing: true, 18 | SilenceUsage: true, 19 | Run: func(thisCmd *cobra.Command, args []string) { 20 | if len(args) == 1 && (args[0] == FlagHelp || args[0] == FlagHelpShort) { 21 | _ = thisCmd.Help() 22 | return 23 | } 24 | 25 | parsedArgs := ProcessFlags(args) 26 | mcpClient, err := CreateClientFunc(parsedArgs) 27 | if err != nil { 28 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) 29 | fmt.Fprintf(os.Stderr, "Example: mcp tools npx -y @modelcontextprotocol/server-filesystem ~\n") 30 | os.Exit(1) 31 | } 32 | 33 | resp, listErr := mcpClient.ListTools(context.Background(), mcp.ListToolsRequest{}) 34 | 35 | var tools []any 36 | if listErr == nil && resp != nil { 37 | tools = ConvertJSONToSlice(resp.Tools) 38 | } 39 | 40 | toolsMap := map[string]any{"tools": tools} 41 | if formatErr := FormatAndPrintResponse(thisCmd, toolsMap, listErr); formatErr != nil { 42 | fmt.Fprintf(os.Stderr, "%v\n", formatErr) 43 | os.Exit(1) 44 | } 45 | }, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/version_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestVersionCmd(t *testing.T) { 9 | buf := new(bytes.Buffer) 10 | 11 | oldVersion := Version 12 | Version = "test-version" 13 | defer func() { Version = oldVersion }() 14 | 15 | // Execute the version command with our buffer 16 | cmd := VersionCmd() 17 | cmd.SetOut(buf) 18 | if err := cmd.Execute(); err != nil { 19 | t.Fatalf("Failed to execute version command: %v", err) 20 | } 21 | 22 | // Read captured output 23 | output := buf.String() 24 | 25 | // Check that the version is in the output 26 | expectedOutput := "MCP Tools version test-version\n" 27 | if output != expectedOutput { 28 | t.Errorf("Expected output %q, got %q", expectedOutput, output) 29 | } 30 | } 31 | 32 | func TestVersionCmdWorks(t *testing.T) { 33 | // Test that the version command can be created and executed 34 | cmd := VersionCmd() 35 | if cmd == nil { 36 | t.Fatal("Expected version command to be created") 37 | } 38 | 39 | // Verify the command properties 40 | if cmd.Use != "version" { 41 | t.Errorf("Expected Use to be 'version', got %q", cmd.Use) 42 | } 43 | 44 | if cmd.Short != "Print the version information" { 45 | t.Errorf("Expected Short to be 'Print the version information', got %q", cmd.Short) 46 | } 47 | 48 | // Ensure Run function is not nil 49 | if cmd.Run == nil { 50 | t.Error("Expected Run function to be defined") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: mcp 2 | 3 | before: 4 | hooks: 5 | - go mod tidy 6 | 7 | builds: 8 | - env: 9 | - CGO_ENABLED=0 10 | goos: 11 | - linux 12 | - windows 13 | - darwin 14 | goarch: 15 | - amd64 16 | - arm64 17 | ldflags: 18 | - -s -w 19 | - -X main.Version={{.Version}} 20 | - -X main.TemplatesPath={{.TemplatesPath}} 21 | main: ./cmd/mcptools/main.go 22 | 23 | archives: 24 | - format: tar.gz 25 | name_template: >- 26 | {{ .ProjectName }}_ 27 | {{- title .Os }}_ 28 | {{- if eq .Arch "amd64" }}x86_64 29 | {{- else if eq .Arch "386" }}i386 30 | {{- else }}{{ .Arch }}{{ end }} 31 | {{- if .Arm }}v{{ .Arm }}{{ end }} 32 | format_overrides: 33 | - goos: windows 34 | format: zip 35 | 36 | checksum: 37 | name_template: 'checksums.txt' 38 | 39 | brews: 40 | - name: mcp 41 | homepage: "https://github.com/f/mcptools" 42 | description: "A CLI tool for interacting with MCP servers" 43 | license: "MIT" 44 | repository: 45 | owner: f 46 | name: homebrew-mcptools 47 | commit_author: 48 | name: goreleaserbot 49 | email: goreleaser@example.com 50 | folder: Formula 51 | install: | 52 | bin.install "mcp" 53 | test: | 54 | system "#{bin}/mcp", "version" 55 | 56 | changelog: 57 | sort: asc 58 | filters: 59 | exclude: 60 | - '^docs:' 61 | - '^test:' 62 | - '^ci:' -------------------------------------------------------------------------------- /cmd/mcptools/commands/prompts.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // PromptsCmd creates the prompts command. 13 | func PromptsCmd() *cobra.Command { 14 | return &cobra.Command{ 15 | Use: "prompts [command args...]", 16 | Short: "List available prompts on the MCP server", 17 | DisableFlagParsing: true, 18 | SilenceUsage: true, 19 | Run: func(thisCmd *cobra.Command, args []string) { 20 | if len(args) == 1 && (args[0] == FlagHelp || args[0] == FlagHelpShort) { 21 | _ = thisCmd.Help() 22 | return 23 | } 24 | 25 | parsedArgs := ProcessFlags(args) 26 | 27 | mcpClient, err := CreateClientFunc(parsedArgs) 28 | if err != nil { 29 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) 30 | fmt.Fprintf(os.Stderr, "Example: mcp prompts npx -y @modelcontextprotocol/server-filesystem ~\n") 31 | os.Exit(1) 32 | } 33 | 34 | resp, listErr := mcpClient.ListPrompts(context.Background(), mcp.ListPromptsRequest{}) 35 | 36 | var prompts []any 37 | if listErr == nil && resp != nil { 38 | prompts = ConvertJSONToSlice(resp.Prompts) 39 | } 40 | 41 | promptsMap := map[string]any{"prompts": prompts} 42 | if formatErr := FormatAndPrintResponse(thisCmd, promptsMap, listErr); formatErr != nil { 43 | fmt.Fprintf(os.Stderr, "%v\n", formatErr) 44 | os.Exit(1) 45 | } 46 | }, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/resources.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // ResourcesCmd creates the resources command. 13 | func ResourcesCmd() *cobra.Command { 14 | return &cobra.Command{ 15 | Use: "resources [command args...]", 16 | Short: "List available resources on the MCP server", 17 | DisableFlagParsing: true, 18 | SilenceUsage: true, 19 | Run: func(thisCmd *cobra.Command, args []string) { 20 | if len(args) == 1 && (args[0] == FlagHelp || args[0] == FlagHelpShort) { 21 | _ = thisCmd.Help() 22 | return 23 | } 24 | 25 | parsedArgs := ProcessFlags(args) 26 | 27 | mcpClient, err := CreateClientFunc(parsedArgs) 28 | if err != nil { 29 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) 30 | fmt.Fprintf(os.Stderr, "Example: mcp resources npx -y @modelcontextprotocol/server-filesystem ~\n") 31 | os.Exit(1) 32 | } 33 | 34 | resp, listErr := mcpClient.ListResources(context.Background(), mcp.ListResourcesRequest{}) 35 | 36 | var resources []any 37 | if listErr == nil && resp != nil { 38 | resources = ConvertJSONToSlice(resp.Resources) 39 | } 40 | 41 | resourcesMap := map[string]any{"resources": resources} 42 | if formatErr := FormatAndPrintResponse(thisCmd, resourcesMap, listErr); formatErr != nil { 43 | fmt.Fprintf(os.Stderr, "%v\n", formatErr) 44 | os.Exit(1) 45 | } 46 | }, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pkg/guard/child_process.go: -------------------------------------------------------------------------------- 1 | package guard 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "os/exec" 8 | ) 9 | 10 | // ChildProcess represents a child process that handles MCP requests. 11 | type ChildProcess struct { 12 | cmd *exec.Cmd 13 | Stdin io.WriteCloser 14 | Stdout io.ReadCloser 15 | Stderr io.ReadCloser 16 | } 17 | 18 | // NewChildProcess creates a new child process. 19 | func NewChildProcess(cmdArgs []string) *ChildProcess { 20 | return &ChildProcess{ 21 | cmd: exec.Command(cmdArgs[0], cmdArgs[1:]...), // nolint:gosec 22 | } 23 | } 24 | 25 | // Start starts the child process. 26 | func (c *ChildProcess) Start() error { 27 | var err error 28 | 29 | // Set up pipes for stdin, stdout, and stderr 30 | c.Stdin, err = c.cmd.StdinPipe() 31 | if err != nil { 32 | return fmt.Errorf("error creating stdin pipe: %w", err) 33 | } 34 | 35 | c.Stdout, err = c.cmd.StdoutPipe() 36 | if err != nil { 37 | return fmt.Errorf("error creating stdout pipe: %w", err) 38 | } 39 | 40 | c.Stderr, err = c.cmd.StderrPipe() 41 | if err != nil { 42 | return fmt.Errorf("error creating stderr pipe: %w", err) 43 | } 44 | 45 | // Start a goroutine to pipe stderr to os.Stderr 46 | go func() { 47 | io.Copy(os.Stderr, c.Stderr) // nolint 48 | }() 49 | 50 | // Start the command 51 | if err := c.cmd.Start(); err != nil { 52 | return fmt.Errorf("error starting command: %w", err) 53 | } 54 | 55 | return nil 56 | } 57 | 58 | // Close closes the child process. 59 | func (c *ChildProcess) Close() error { 60 | if c.cmd.Process != nil { 61 | return c.cmd.Process.Kill() 62 | } 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/prompts_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestPromptsCmdRun_Help(t *testing.T) { 9 | // Test that the help flag displays help text 10 | cmd := PromptsCmd() 11 | buf := new(bytes.Buffer) 12 | cmd.SetOut(buf) 13 | 14 | // Execute with help flag 15 | cmd.SetArgs([]string{"--help"}) 16 | err := cmd.Execute() 17 | if err != nil { 18 | t.Errorf("cmd.Execute() error = %v", err) 19 | } 20 | 21 | // Check that help output is not empty 22 | if buf.String() == "" { 23 | t.Error("Expected help output, got empty string") 24 | } 25 | } 26 | 27 | func TestPromptsCmdRun_Success(t *testing.T) { 28 | // Create a mock client that returns successful response 29 | mockResponse := map[string]any{ 30 | "prompts": []any{ 31 | map[string]any{ 32 | "name": "test-prompt", 33 | "description": "Test prompt description", 34 | }, 35 | }, 36 | } 37 | 38 | cleanup := setupMockClient(func(method string, _ any) (map[string]any, error) { 39 | if method != "prompts/list" { 40 | t.Errorf("Expected method 'prompts/list', got %q", method) 41 | } 42 | return mockResponse, nil 43 | }) 44 | defer cleanup() 45 | 46 | // Set up command 47 | cmd := PromptsCmd() 48 | buf := new(bytes.Buffer) 49 | cmd.SetOut(buf) 50 | 51 | // Execute command 52 | cmd.SetArgs([]string{"server", "arg"}) 53 | err := cmd.Execute() 54 | if err != nil { 55 | t.Errorf("cmd.Execute() error = %v", err) 56 | } 57 | 58 | // Verify output contains expected content 59 | output := buf.String() 60 | assertContains(t, output, "test-prompt") 61 | assertContains(t, output, "Test prompt description") 62 | } 63 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/tools_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestToolsCmdRun_Help(t *testing.T) { 9 | // Test that the help flag displays help text 10 | cmd := ToolsCmd() 11 | buf := new(bytes.Buffer) 12 | cmd.SetOut(buf) 13 | 14 | // Execute with help flag 15 | cmd.SetArgs([]string{"--help"}) 16 | err := cmd.Execute() 17 | if err != nil { 18 | t.Errorf("cmd.Execute() error = %v", err) 19 | } 20 | 21 | // Check that help output is not empty 22 | if buf.String() == "" { 23 | t.Error("Expected help output, got empty string") 24 | } 25 | } 26 | 27 | func TestToolsCmdRun(t *testing.T) { 28 | // Save original format option 29 | origFormatOption := FormatOption 30 | defer func() { FormatOption = origFormatOption }() 31 | 32 | // Create a mock client that returns successful response 33 | mockResponse := map[string]any{ 34 | "tools": []any{ 35 | map[string]any{ 36 | "name": "test-tool", 37 | "description": "A test tool", 38 | }, 39 | }, 40 | } 41 | 42 | cleanup := setupMockClient(func(method string, _ any) (map[string]any, error) { 43 | if method != "tools/list" { 44 | t.Errorf("Expected method 'tools/list', got %q", method) 45 | } 46 | return mockResponse, nil 47 | }) 48 | defer cleanup() 49 | 50 | // Set up command 51 | cmd := ToolsCmd() 52 | buf := new(bytes.Buffer) 53 | cmd.SetOut(buf) 54 | cmd.SetArgs([]string{"server", "args"}) 55 | err := cmd.Execute() 56 | if err != nil { 57 | t.Errorf("cmd.Execute() error = %v", err) 58 | } 59 | output := buf.String() 60 | assertContains(t, output, "test-tool") 61 | assertContains(t, output, "A test tool") 62 | } 63 | -------------------------------------------------------------------------------- /cmd/mcptools/transport_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/f/mcptools/cmd/mcptools/commands" 7 | ) 8 | 9 | func TestTransportFlag(t *testing.T) { 10 | // Save original values 11 | origTransport := commands.TransportOption 12 | 13 | // Test default value 14 | if commands.TransportOption != "http" { 15 | t.Errorf("Expected default transport to be 'http', got '%s'", commands.TransportOption) 16 | } 17 | 18 | // Test ProcessFlags with transport flag 19 | args := []string{"tools", "--transport", "sse", "http://localhost:3000"} 20 | remainingArgs := commands.ProcessFlags(args) 21 | 22 | expectedArgs := []string{"tools", "http://localhost:3000"} 23 | if len(remainingArgs) != len(expectedArgs) { 24 | t.Errorf("Expected %d args, got %d", len(expectedArgs), len(remainingArgs)) 25 | } 26 | 27 | for i, arg := range expectedArgs { 28 | if remainingArgs[i] != arg { 29 | t.Errorf("Expected arg %d to be '%s', got '%s'", i, arg, remainingArgs[i]) 30 | } 31 | } 32 | 33 | if commands.TransportOption != "sse" { 34 | t.Errorf("Expected transport to be 'sse', got '%s'", commands.TransportOption) 35 | } 36 | 37 | // Restore original values 38 | commands.TransportOption = origTransport 39 | } 40 | 41 | func TestIsHTTP(t *testing.T) { 42 | testCases := []struct { 43 | url string 44 | expected bool 45 | }{ 46 | {"http://localhost:3000", true}, 47 | {"https://example.com", true}, 48 | {"localhost:3000", true}, 49 | {"stdio", false}, 50 | {"", false}, 51 | {"file:///path", false}, 52 | } 53 | 54 | for _, tc := range testCases { 55 | result := commands.IsHTTP(tc.url) 56 | if result != tc.expected { 57 | t.Errorf("IsHTTP(%s) = %v, expected %v", tc.url, result, tc.expected) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to MCPTools 2 | 3 | Thank you for considering contributing to MCPTools! This document provides guidelines and instructions for contributing to the project. 4 | 5 | ## Getting Started 6 | 7 | 1. Fork the repository 8 | 2. Clone your fork: `git clone https://github.com/YOUR-USERNAME/mcptools.git` 9 | 3. Create a new branch: `git checkout -b feature/your-feature-name` 10 | 11 | ## Development Setup 12 | 13 | 1. No need to pre-install Go - the setup process will automatically install it if needed 14 | 2. Run `make setup` to set up the development environment (this will check/install Go and set up everything else) 15 | 3. Make your changes 16 | 4. Test your local changes by running `make build` first, then `./bin/mcp [command]` 17 | 5. Run tests using `make test` 18 | 6. Run the linter using `make lint` 19 | 20 | ## Pull Request Process 21 | 22 | 1. Update the README.md with details of changes if needed 23 | 2. Follow the existing code style and formatting 24 | 3. Add tests for new features 25 | 4. Ensure all tests pass and the linter shows no errors 26 | 5. Update documentation as needed 27 | 28 | ## Commit Messages 29 | 30 | - Use clear and meaningful commit messages 31 | - Start with a verb in present tense (e.g., "Add feature" not "Added feature") 32 | - Reference issue numbers if applicable 33 | 34 | ## Code Style 35 | 36 | - Follow Go best practices and idioms 37 | - Use meaningful variable and function names 38 | - Add comments for complex logic 39 | - Keep functions focused and small 40 | 41 | ## Questions or Problems? 42 | 43 | Feel free to open an issue for any questions or problems you encounter. 44 | 45 | ## License 46 | 47 | By contributing, you agree that your contributions will be licensed under the same terms as the main project. 48 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/resources_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestResourcesCmdRun_Help(t *testing.T) { 9 | // Test that the help flag displays help text 10 | cmd := ResourcesCmd() 11 | buf := new(bytes.Buffer) 12 | cmd.SetOut(buf) 13 | 14 | // Execute with help flag 15 | cmd.SetArgs([]string{"--help"}) 16 | err := cmd.Execute() 17 | if err != nil { 18 | t.Errorf("cmd.Execute() error = %v", err) 19 | } 20 | 21 | // Check that help output is not empty 22 | if buf.String() == "" { 23 | t.Error("Expected help output, got empty string") 24 | } 25 | } 26 | 27 | func TestResourcesCmdRun_Success(t *testing.T) { 28 | // Create a mock client that returns successful response 29 | mockResponse := map[string]any{ 30 | "resources": []any{ 31 | map[string]any{ 32 | "uri": "test://resource", 33 | "mimeType": "text/plain", 34 | "name": "TestResource", 35 | "description": "Test resource description", 36 | }, 37 | }, 38 | } 39 | 40 | cleanup := setupMockClient(func(method string, _ any) (map[string]any, error) { 41 | if method != "resources/list" { 42 | t.Errorf("Expected method 'resources/list', got %q", method) 43 | } 44 | return mockResponse, nil 45 | }) 46 | defer cleanup() 47 | 48 | // Set up command 49 | cmd := ResourcesCmd() 50 | buf := new(bytes.Buffer) 51 | cmd.SetOut(buf) 52 | 53 | // Execute command 54 | cmd.SetArgs([]string{"server", "arg"}) 55 | err := cmd.Execute() 56 | if err != nil { 57 | t.Errorf("cmd.Execute() error = %v", err) 58 | } 59 | 60 | // Verify output contains expected content 61 | output := buf.String() 62 | assertContains(t, output, "TestResource") 63 | assertContains(t, output, "test://resource") 64 | assertContains(t, output, "text/plain") 65 | assertContains(t, output, "Test resource description") 66 | } 67 | -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | # MCP Project Templates 2 | 3 | This directory contains templates for creating MCP (Model Context Protocol) servers with different capabilities. 4 | 5 | ## Installation 6 | 7 | The templates will be automatically found if they are in one of these locations: 8 | 9 | 1. `./templates/`: Local project directory 10 | 2. `~/.mcpt/templates/`: User's home directory 11 | 3. Next to the executable in the installed location 12 | 13 | To install templates to your home directory: 14 | 15 | ```bash 16 | make templates 17 | ``` 18 | 19 | ## Usage 20 | 21 | Create a new MCP project with the `mcp new` command: 22 | 23 | ```bash 24 | # Create a project with a tool, resource, and prompt 25 | mcp new tool:hello_world resource:file prompt:hello 26 | 27 | # Create a project with a specific SDK (currently only TypeScript/ts supported) 28 | mcp new tool:hello_world --sdk=ts 29 | 30 | # Create a project with a specific transport (stdio, sse, or http) 31 | mcp new tool:hello_world --transport=stdio 32 | mcp new tool:hello_world --transport=sse 33 | mcp new tool:hello_world --transport=http 34 | ``` 35 | 36 | ## Available Templates 37 | 38 | ### TypeScript (ts) 39 | 40 | - **tool**: Basic tool implementation template 41 | - **resource**: Resource implementation template 42 | - **prompt**: Prompt implementation template 43 | - **server_stdio**: Server with stdio transport 44 | - **server_sse**: Server with SSE transport 45 | - **server_http**: Server with streamable HTTP transport 46 | - **full_server**: Complete server with all three capabilities 47 | 48 | ## Project Structure 49 | 50 | The scaffolding creates the following structure: 51 | 52 | ``` 53 | my-project/ 54 | ├── package.json 55 | ├── tsconfig.json 56 | └── src/ 57 | ├── index.ts 58 | └── [component].ts 59 | ``` 60 | 61 | After scaffolding, run: 62 | 63 | ```bash 64 | npm install 65 | npm run build 66 | npm start 67 | ``` -------------------------------------------------------------------------------- /cmd/mcptools/commands/read_resource_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestResourceReadCmd_RunHelp(t *testing.T) { 9 | // Given: the read resource command is run 10 | cmd := ReadResourceCmd() 11 | buf := new(bytes.Buffer) 12 | cmd.SetOut(buf) 13 | 14 | // When: the command is run with the help flag 15 | cmd.SetArgs([]string{"--help"}) 16 | 17 | // Then: no error is returned. 18 | err := cmd.Execute() 19 | if err != nil { 20 | t.Errorf("cmd.Execute() error = %v", err) 21 | } 22 | 23 | // Then: the help output is not empty. 24 | if buf.String() == "" { 25 | t.Error("Expected help output to not be empty.") 26 | } 27 | } 28 | 29 | func TestReadResourceCmdRun_Success(t *testing.T) { 30 | t.Setenv("COLS", "120") 31 | // Given: the read resource command is run 32 | cmd := ReadResourceCmd() 33 | buf := new(bytes.Buffer) 34 | cmd.SetOut(buf) 35 | // NOTE: we currently truncate output in tabular output. 36 | cmd.SetArgs([]string{"server", "-f", "json", "arg"}) 37 | 38 | // Given: a mock client that returns a successful read resource response 39 | mockResponse := map[string]any{ 40 | "contents": []any{ 41 | map[string]any{ 42 | "uri": "test://foo", 43 | "mimeType": "text/plain", 44 | "text": "bar", 45 | }, 46 | }, 47 | } 48 | 49 | cleanup := setupMockClient(func(method string, _ any) (map[string]any, error) { 50 | if method != "resources/read" { 51 | t.Errorf("Expected method 'resources/read', got %q", method) 52 | } 53 | return mockResponse, nil 54 | }) 55 | defer cleanup() 56 | 57 | // When: the command is executed 58 | err := cmd.Execute() 59 | // Then: no error is returned 60 | if err != nil { 61 | t.Errorf("cmd.Execute() error = %v", err) 62 | } 63 | 64 | // Then: the expected content is returned 65 | output := buf.String() 66 | assertContains(t, output, "test://foo") 67 | assertContains(t, output, "text/plain") 68 | assertContains(t, output, "bar") 69 | } 70 | -------------------------------------------------------------------------------- /scripts/check_go.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -o pipefail 5 | set -o errexit 6 | set -o nounset 7 | 8 | check_go() { 9 | if ! command -v go &> /dev/null; then 10 | printf "Go is not installed on your system.\n" 11 | read -p -r "Would you like to install Go now? (y/n): " choice 12 | 13 | case "$choice" in 14 | y|Y) 15 | printf "Installing Go...\n" 16 | 17 | # Detect OS 18 | case "$(uname -s)" in 19 | Darwin) 20 | if command -v brew &> /dev/null; then 21 | brew install go 22 | else 23 | printf "Homebrew not found. Please install Go manually from https://golang.org/dl/\n" 24 | exit 1 25 | fi 26 | ;; 27 | Linux) 28 | if command -v apt-get &> /dev/null; then 29 | sudo apt-get update && sudo apt-get install -y golang-go 30 | elif command -v yum &> /dev/null; then 31 | sudo yum install -y golang 32 | else 33 | printf "Package manager not found. Please install Go manually from https://golang.org/dl/\n" 34 | exit 1 35 | fi 36 | ;; 37 | MINGW*|MSYS*|CYGWIN*) 38 | printf "On Windows, please install Go manually from https://golang.org/dl/\n" 39 | exit 1 40 | ;; 41 | *) 42 | printf "Unsupported operating system. Please install Go manually from https://golang.org/dl/\n" 43 | exit 1 44 | ;; 45 | esac 46 | 47 | # Verify installation 48 | if ! command -v go &> /dev/null; then 49 | printf "Go installation failed.\n" 50 | exit 1 51 | fi 52 | printf "Go has been installed successfully!\n" 53 | ;; 54 | *) 55 | printf "Go installation skipped. Please install Go before building.\n" 56 | exit 1 57 | ;; 58 | esac 59 | else 60 | printf "Go is already installed. Version: %s\n" "$(go version)" 61 | return 0 62 | fi 63 | } 64 | 65 | check_go -------------------------------------------------------------------------------- /cmd/mcptools/commands/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package commands implements individual commands for the MCP CLI. 3 | */ 4 | package commands 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // flags. 11 | const ( 12 | FlagFormat = "--format" 13 | FlagFormatShort = "-f" 14 | FlagParams = "--params" 15 | FlagParamsShort = "-p" 16 | FlagHelp = "--help" 17 | FlagHelpShort = "-h" 18 | FlagServerLogs = "--server-logs" 19 | FlagTransport = "--transport" 20 | FlagAuthUser = "--auth-user" 21 | FlagAuthHeader = "--auth-header" 22 | ) 23 | 24 | // entity types. 25 | const ( 26 | EntityTypeTool = "tool" 27 | EntityTypePrompt = "prompt" 28 | EntityTypeRes = "resource" 29 | ) 30 | 31 | var ( 32 | // FormatOption is the format option for the command, valid values are "table", "json", and 33 | // "pretty". 34 | // Default is "table". 35 | FormatOption = "table" 36 | // ParamsString is the params for the command. 37 | ParamsString string 38 | // ShowServerLogs is a flag to show server logs. 39 | ShowServerLogs bool 40 | // TransportOption is the transport option for HTTP connections, valid values are "sse" and "http". 41 | // Default is "http" (streamable HTTP). 42 | TransportOption = "http" 43 | // AuthUser contains username:password for basic authentication. 44 | AuthUser string 45 | // AuthHeader is a custom Authorization header. 46 | AuthHeader string 47 | ) 48 | 49 | // RootCmd creates the root command. 50 | func RootCmd() *cobra.Command { 51 | cmd := &cobra.Command{ 52 | Use: "mcp", 53 | Short: "MCP is a command line interface for interacting with MCP servers", 54 | Long: `MCP is a command line interface for interacting with Model Context Protocol (MCP) servers. 55 | It allows you to discover and call tools, list resources, and interact with MCP-compatible services.`, 56 | } 57 | 58 | cmd.PersistentFlags().StringVarP(&FormatOption, "format", "f", "table", "Output format (table, json, pretty)") 59 | cmd.PersistentFlags(). 60 | StringVarP(&ParamsString, "params", "p", "{}", "JSON string of parameters to pass to the tool (for call command)") 61 | cmd.PersistentFlags().StringVar(&TransportOption, "transport", "http", "HTTP transport type (http, sse)") 62 | cmd.PersistentFlags().StringVar(&AuthUser, "auth-user", "", "Basic authentication in username:password format") 63 | cmd.PersistentFlags().StringVar(&AuthHeader, "auth-header", "", "Custom Authorization header (e.g., 'Bearer token' or 'Basic base64credentials')") 64 | 65 | return cmd 66 | } 67 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/read_resource.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/mark3labs/mcp-go/mcp" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // ReadResourceCmd creates the read-resource command. 13 | func ReadResourceCmd() *cobra.Command { 14 | return &cobra.Command{ 15 | Use: "read-resource resource [command args...]", 16 | Short: "Read a resource on the MCP server", 17 | DisableFlagParsing: true, 18 | SilenceUsage: true, 19 | Run: func(thisCmd *cobra.Command, args []string) { 20 | if len(args) == 1 && (args[0] == FlagHelp || args[0] == FlagHelpShort) { 21 | _ = thisCmd.Help() 22 | return 23 | } 24 | 25 | if len(args) == 0 { 26 | fmt.Fprintln(os.Stderr, "Error: resource name is required") 27 | fmt.Fprintln( 28 | os.Stderr, 29 | "Example: mcp read-resource test://static/resource/1 npx -y @modelcontextprotocol/server-filesystem ~", 30 | ) 31 | os.Exit(1) 32 | } 33 | 34 | cmdArgs := args 35 | parsedArgs := []string{} 36 | resourceName := "" 37 | 38 | i := 0 39 | resourceExtracted := false 40 | 41 | for i < len(cmdArgs) { 42 | switch { 43 | case (cmdArgs[i] == FlagFormat || cmdArgs[i] == FlagFormatShort) && i+1 < len(cmdArgs): 44 | FormatOption = cmdArgs[i+1] 45 | i += 2 46 | case (cmdArgs[i] == FlagParams || cmdArgs[i] == FlagParamsShort) && i+1 < len(cmdArgs): 47 | ParamsString = cmdArgs[i+1] 48 | i += 2 49 | case !resourceExtracted: 50 | resourceName = cmdArgs[i] 51 | resourceExtracted = true 52 | i++ 53 | default: 54 | parsedArgs = append(parsedArgs, cmdArgs[i]) 55 | i++ 56 | } 57 | } 58 | 59 | if resourceName == "" { 60 | fmt.Fprintln(os.Stderr, "Error: resource name is required") 61 | fmt.Fprintln( 62 | os.Stderr, 63 | "Example: mcp read-resource test://static/resource/1 npx -y @modelcontextprotocol/server-filesystem ~", 64 | ) 65 | os.Exit(1) 66 | } 67 | 68 | mcpClient, clientErr := CreateClientFunc(parsedArgs) 69 | if clientErr != nil { 70 | fmt.Fprintf(os.Stderr, "Error: %v\n", clientErr) 71 | os.Exit(1) 72 | } 73 | 74 | request := mcp.ReadResourceRequest{} 75 | request.Params.URI = resourceName 76 | resp, execErr := mcpClient.ReadResource(context.Background(), request) 77 | 78 | var responseMap map[string]any 79 | if execErr == nil && resp != nil { 80 | responseMap = ConvertJSONToMap(resp) 81 | } else { 82 | responseMap = map[string]any{} 83 | } 84 | 85 | if formatErr := FormatAndPrintResponse(thisCmd, responseMap, execErr); formatErr != nil { 86 | fmt.Fprintf(os.Stderr, "%v\n", formatErr) 87 | os.Exit(1) 88 | } 89 | }, 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /pkg/alias/alias.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package alias implements server alias functionality for MCP. 3 | */ 4 | package alias 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | // ServerAlias represents a single server command alias. 14 | type ServerAlias struct { 15 | Command string `json:"command"` 16 | } 17 | 18 | // Aliases stores command aliases for MCP servers. 19 | type Aliases map[string]ServerAlias 20 | 21 | // GetConfigPath returns the path to the aliases configuration file. 22 | func GetConfigPath() (string, error) { 23 | homeDir, err := os.UserHomeDir() 24 | if err != nil { 25 | return "", fmt.Errorf("failed to get user home directory: %w", err) 26 | } 27 | 28 | configDir := filepath.Join(homeDir, ".mcpt") 29 | mkdirErr := os.MkdirAll(configDir, 0o750) 30 | if mkdirErr != nil { 31 | return "", fmt.Errorf("failed to create config directory: %w", mkdirErr) 32 | } 33 | 34 | return filepath.Join(configDir, "aliases.json"), nil 35 | } 36 | 37 | // Load loads server aliases from the configuration file. 38 | func Load() (Aliases, error) { 39 | configPath, err := GetConfigPath() 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | aliases := make(Aliases) 45 | 46 | var statErr error 47 | if _, statErr = os.Stat(configPath); os.IsNotExist(statErr) { 48 | return aliases, nil 49 | } 50 | 51 | configFile, err := os.ReadFile(configPath) // #nosec G304 - configPath is generated internally by GetConfigPath 52 | if err != nil { 53 | return nil, fmt.Errorf("failed to read alias config file: %w", err) 54 | } 55 | 56 | if len(configFile) == 0 { 57 | return aliases, nil 58 | } 59 | 60 | if unmarshalErr := json.Unmarshal(configFile, &aliases); unmarshalErr != nil { 61 | return nil, fmt.Errorf("failed to parse alias config file: %w", unmarshalErr) 62 | } 63 | 64 | return aliases, nil 65 | } 66 | 67 | // Save saves server aliases to the configuration file. 68 | func Save(aliases Aliases) error { 69 | configPath, err := GetConfigPath() 70 | if err != nil { 71 | return err 72 | } 73 | 74 | configJSON, err := json.MarshalIndent(aliases, "", " ") 75 | if err != nil { 76 | return fmt.Errorf("failed to marshal alias config: %w", err) 77 | } 78 | 79 | writeErr := os.WriteFile(configPath, configJSON, 0o600) // #nosec G304 - configPath is generated internally by GetConfigPath 80 | if writeErr != nil { 81 | return fmt.Errorf("failed to write alias config file: %w", writeErr) 82 | } 83 | 84 | return nil 85 | } 86 | 87 | // GetServerCommand retrieves the server command for a given alias. 88 | func GetServerCommand(aliasName string) (string, bool) { 89 | aliases, err := Load() 90 | if err != nil { 91 | return "", false 92 | } 93 | 94 | alias, exists := aliases[aliasName] 95 | if !exists { 96 | return "", false 97 | } 98 | 99 | return alias.Command, true 100 | } 101 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/call_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestCallCmdRun_Help(t *testing.T) { 10 | // Test that the help flag displays help text 11 | cmd := CallCmd() 12 | buf := new(bytes.Buffer) 13 | cmd.SetOut(buf) 14 | 15 | // Execute with help flag 16 | cmd.SetArgs([]string{"--help"}) 17 | err := cmd.Execute() 18 | if err != nil { 19 | t.Errorf("cmd.Execute() error = %v", err) 20 | } 21 | 22 | // Check that help output is not empty 23 | if buf.String() == "" { 24 | t.Error("Expected help output, got empty string") 25 | } 26 | } 27 | 28 | func TestCallCmdRun_Tool(t *testing.T) { 29 | // Create a mock client that returns successful response 30 | mockResponse := map[string]any{ 31 | "content": []any{ 32 | map[string]any{ 33 | "type": "text", 34 | "text": "Tool executed successfully", 35 | }, 36 | }, 37 | } 38 | 39 | cleanup := setupMockClient(func(method string, _ any) (map[string]any, error) { 40 | if method != "tools/call" { 41 | t.Errorf("Expected method 'tools/call', got %q", method) 42 | } 43 | return mockResponse, nil 44 | }) 45 | defer cleanup() 46 | 47 | // Set up command 48 | cmd := CallCmd() 49 | buf := new(bytes.Buffer) 50 | cmd.SetOut(buf) 51 | 52 | // Execute command with tool 53 | cmd.SetArgs([]string{"test-tool", "--params", `{"key":"value"}`, "server", "arg"}) 54 | err := cmd.Execute() 55 | if err != nil { 56 | t.Errorf("cmd.Execute() error = %v", err) 57 | } 58 | 59 | // Verify output contains expected content 60 | output := strings.TrimSpace(buf.String()) 61 | expectedOutput := "Tool executed successfully" 62 | assertEquals(t, output, expectedOutput) 63 | } 64 | 65 | func TestCallCmdRun_Resource(t *testing.T) { 66 | // Create a mock client that returns successful response 67 | mockResponse := map[string]any{ 68 | "contents": []any{ 69 | map[string]any{ 70 | "uri": "test://foo", 71 | "mimeType": "text/plain", 72 | "text": "bar", 73 | }, 74 | }, 75 | } 76 | 77 | cleanup := setupMockClient(func(method string, _ any) (map[string]any, error) { 78 | if method != "resources/read" { 79 | t.Errorf("Expected method 'resources/read', got %q", method) 80 | } 81 | return mockResponse, nil 82 | }) 83 | defer cleanup() 84 | 85 | // Set up command 86 | cmd := CallCmd() 87 | buf := new(bytes.Buffer) 88 | cmd.SetOut(buf) 89 | 90 | // Execute command with resource 91 | cmd.SetArgs([]string{"resource:test-resource", "-f", "json", "server", "arg"}) 92 | err := cmd.Execute() 93 | if err != nil { 94 | t.Errorf("cmd.Execute() error = %v", err) 95 | } 96 | 97 | // Verify output contains expected content 98 | output := buf.String() 99 | expectedOutput := `{"contents":[{"mimeType":"text/plain","text":"bar","uri":"test://foo"}]}` 100 | assertContains(t, output, expectedOutput) 101 | } 102 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/get_prompt.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // GetPromptCmd creates the get-prompt command. 14 | func GetPromptCmd() *cobra.Command { 15 | return &cobra.Command{ 16 | Use: "get-prompt prompt [command args...]", 17 | Short: "Get a prompt on the MCP server", 18 | DisableFlagParsing: true, 19 | SilenceUsage: true, 20 | Run: func(thisCmd *cobra.Command, args []string) { 21 | if len(args) == 1 && (args[0] == FlagHelp || args[0] == FlagHelpShort) { 22 | _ = thisCmd.Help() 23 | return 24 | } 25 | 26 | if len(args) == 0 { 27 | fmt.Fprintln(os.Stderr, "Error: prompt name is required") 28 | fmt.Fprintln( 29 | os.Stderr, 30 | "Example: mcp get-prompt read_file npx -y @modelcontextprotocol/server-filesystem ~", 31 | ) 32 | os.Exit(1) 33 | } 34 | 35 | cmdArgs := args 36 | parsedArgs := []string{} 37 | promptName := "" 38 | 39 | i := 0 40 | promptExtracted := false 41 | 42 | for i < len(cmdArgs) { 43 | switch { 44 | case (cmdArgs[i] == FlagFormat || cmdArgs[i] == FlagFormatShort) && i+1 < len(cmdArgs): 45 | FormatOption = cmdArgs[i+1] 46 | i += 2 47 | case (cmdArgs[i] == FlagParams || cmdArgs[i] == FlagParamsShort) && i+1 < len(cmdArgs): 48 | ParamsString = cmdArgs[i+1] 49 | i += 2 50 | case cmdArgs[i] == FlagServerLogs: 51 | ShowServerLogs = true 52 | i++ 53 | case !promptExtracted: 54 | promptName = cmdArgs[i] 55 | promptExtracted = true 56 | i++ 57 | default: 58 | parsedArgs = append(parsedArgs, cmdArgs[i]) 59 | i++ 60 | } 61 | } 62 | 63 | if promptName == "" { 64 | fmt.Fprintln(os.Stderr, "Error: prompt name is required") 65 | fmt.Fprintln( 66 | os.Stderr, 67 | "Example: mcp get-prompt read_file npx -y @modelcontextprotocol/server-filesystem ~", 68 | ) 69 | os.Exit(1) 70 | } 71 | 72 | var params map[string]any 73 | if ParamsString != "" { 74 | if jsonErr := json.Unmarshal([]byte(ParamsString), ¶ms); jsonErr != nil { 75 | fmt.Fprintf(os.Stderr, "Error: invalid JSON for params: %v\n", jsonErr) 76 | os.Exit(1) 77 | } 78 | } 79 | 80 | mcpClient, clientErr := CreateClientFunc(parsedArgs) 81 | if clientErr != nil { 82 | fmt.Fprintf(os.Stderr, "Error: %v\n", clientErr) 83 | os.Exit(1) 84 | } 85 | 86 | request := mcp.GetPromptRequest{} 87 | request.Params.Name = promptName 88 | resp, execErr := mcpClient.GetPrompt(context.Background(), request) 89 | 90 | var responseMap map[string]any 91 | if execErr == nil && resp != nil { 92 | responseMap = ConvertJSONToMap(resp) 93 | } else { 94 | responseMap = map[string]any{} 95 | } 96 | 97 | if formatErr := FormatAndPrintResponse(thisCmd, responseMap, execErr); formatErr != nil { 98 | fmt.Fprintf(os.Stderr, "%v\n", formatErr) 99 | os.Exit(1) 100 | } 101 | }, 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/test_helpers.go: -------------------------------------------------------------------------------- 1 | // Package commands implements individual commands for the MCP CLI. 2 | package commands 3 | 4 | import ( 5 | "bytes" 6 | "context" 7 | "encoding/json" 8 | "fmt" 9 | "testing" 10 | 11 | "github.com/mark3labs/mcp-go/client" 12 | "github.com/mark3labs/mcp-go/client/transport" 13 | "github.com/mark3labs/mcp-go/mcp" 14 | ) 15 | 16 | // MockTransport implements the transport.Transport interface for testing. 17 | type MockTransport struct { 18 | ExecuteFunc func(method string, params any) (map[string]any, error) 19 | } 20 | 21 | // Start is a no-op for the mock transport. 22 | func (m *MockTransport) Start(_ context.Context) error { 23 | return nil 24 | } 25 | 26 | // SendRequest overrides the default implementation of the transport.SendRequest method. 27 | func (m *MockTransport) SendRequest(_ context.Context, request transport.JSONRPCRequest) (*transport.JSONRPCResponse, error) { 28 | if request.Method == "initialize" { 29 | return &transport.JSONRPCResponse{Result: json.RawMessage(`{}`)}, nil 30 | } 31 | response, err := m.ExecuteFunc(request.Method, request.Params) 32 | if err != nil { 33 | return nil, err 34 | } 35 | responseBytes, err := json.Marshal(response) 36 | if err != nil { 37 | return nil, err 38 | } 39 | fmt.Println("Returning response:", string(responseBytes)) 40 | return &transport.JSONRPCResponse{Result: json.RawMessage(responseBytes)}, nil 41 | } 42 | 43 | // SendNotification is a no-op for the mock transport. 44 | func (m *MockTransport) SendNotification(_ context.Context, _ mcp.JSONRPCNotification) error { 45 | return nil 46 | } 47 | 48 | // SetNotificationHandler is a no-op for the mock transport. 49 | func (m *MockTransport) SetNotificationHandler(_ func(notification mcp.JSONRPCNotification)) { 50 | } 51 | 52 | // Close is a no-op for the mock transport. 53 | func (m *MockTransport) Close() error { 54 | return nil 55 | } 56 | 57 | // GetSessionId returns an empty session ID for the mock transport. 58 | func (m *MockTransport) GetSessionId() string { 59 | return "" 60 | } 61 | 62 | // setupMockClient creates a mock client with the given execute function and returns cleanup function. 63 | func setupMockClient(executeFunc func(method string, _ any) (map[string]any, error)) func() { 64 | // Save original function and restore later 65 | originalFunc := CreateClientFunc 66 | 67 | mockTransport := &MockTransport{ 68 | ExecuteFunc: executeFunc, 69 | } 70 | 71 | mockClient := client.NewClient(mockTransport) 72 | _, _ = mockClient.Initialize(context.Background(), mcp.InitializeRequest{}) 73 | 74 | // Override the function that creates clients 75 | CreateClientFunc = func(_ []string, _ ...client.ClientOption) (*client.Client, error) { 76 | return mockClient, nil 77 | } 78 | 79 | // Return a cleanup function 80 | return func() { 81 | CreateClientFunc = originalFunc 82 | } 83 | } 84 | 85 | // assertContains checks if the output contains the expected string. 86 | func assertContains(t *testing.T, output string, expected string) { 87 | t.Helper() 88 | if !bytes.Contains([]byte(output), []byte(expected)) { 89 | t.Errorf("Expected output to contain %q, got: %s", expected, output) 90 | } 91 | } 92 | 93 | // assertEqual checks if two values are equal. 94 | func assertEquals(t *testing.T, output string, expected string) { 95 | t.Helper() 96 | if output != expected { 97 | t.Errorf("Expected %v, got %v", expected, output) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/alias.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/f/mcptools/pkg/alias" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // AliasCmd creates the alias command. 12 | func AliasCmd() *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "alias", 15 | Short: "Manage MCP server aliases", 16 | Long: `Manage aliases for MCP servers. 17 | 18 | This command allows you to register MCP server commands with a friendly name and 19 | reuse them later. 20 | 21 | Aliases are stored in $HOME/.mcpt/aliases.json. 22 | 23 | Examples: 24 | # Add a new server alias 25 | mcp alias add myfs npx -y @modelcontextprotocol/server-filesystem ~/ 26 | 27 | # List all registered server aliases 28 | mcp alias list 29 | 30 | # Remove a server alias 31 | mcp alias remove myfs 32 | 33 | # Use an alias with any MCP command 34 | mcp tools myfs`, 35 | } 36 | 37 | cmd.AddCommand(aliasAddCmd()) 38 | cmd.AddCommand(aliasListCmd()) 39 | cmd.AddCommand(aliasRemoveCmd()) 40 | 41 | return cmd 42 | } 43 | 44 | func aliasAddCmd() *cobra.Command { 45 | addCmd := &cobra.Command{ 46 | Use: "add [alias] [command args...]", 47 | Short: "Add a new MCP server alias", 48 | DisableFlagParsing: true, 49 | Long: `Add a new alias for an MCP server command. 50 | 51 | The alias will be registered and can be used in place of the server command. 52 | 53 | Example: 54 | mcp alias add myfs npx -y @modelcontextprotocol/server-filesystem ~/`, 55 | Args: cobra.MinimumNArgs(2), 56 | RunE: func(thisCmd *cobra.Command, args []string) error { 57 | if len(args) == 1 && (args[0] == FlagHelp || args[0] == FlagHelpShort) { 58 | _ = thisCmd.Help() 59 | return nil 60 | } 61 | 62 | aliasName := args[0] 63 | serverCommand := strings.Join(args[1:], " ") 64 | 65 | aliases, err := alias.Load() 66 | if err != nil { 67 | return fmt.Errorf("error loading aliases: %w", err) 68 | } 69 | 70 | aliases[aliasName] = alias.ServerAlias{ 71 | Command: serverCommand, 72 | } 73 | 74 | if saveErr := alias.Save(aliases); saveErr != nil { 75 | return fmt.Errorf("error saving aliases: %w", saveErr) 76 | } 77 | 78 | fmt.Fprintf(thisCmd.OutOrStdout(), "Alias '%s' registered for command: %s\n", aliasName, serverCommand) 79 | return nil 80 | }, 81 | } 82 | return addCmd 83 | } 84 | 85 | func aliasListCmd() *cobra.Command { 86 | return &cobra.Command{ 87 | Use: "list", 88 | Short: "List all registered MCP server aliases", 89 | RunE: func(cmd *cobra.Command, _ []string) error { 90 | // Load existing aliases 91 | aliases, err := alias.Load() 92 | if err != nil { 93 | return fmt.Errorf("error loading aliases: %w", err) 94 | } 95 | 96 | if len(aliases) == 0 { 97 | fmt.Fprintln(cmd.OutOrStdout(), "No aliases registered.") 98 | return nil 99 | } 100 | 101 | fmt.Fprintln(cmd.OutOrStdout(), "Registered MCP server aliases:") 102 | for name, a := range aliases { 103 | fmt.Fprintf(cmd.OutOrStdout(), " %s: %s\n", name, a.Command) 104 | } 105 | 106 | return nil 107 | }, 108 | } 109 | } 110 | 111 | func aliasRemoveCmd() *cobra.Command { 112 | return &cobra.Command{ 113 | Use: "remove ", 114 | Short: "Remove an MCP server alias", 115 | Long: `Remove a registered alias for an MCP server command. 116 | 117 | Example: 118 | mcp alias remove myfs`, 119 | Args: cobra.ExactArgs(1), 120 | RunE: func(thisCmd *cobra.Command, args []string) error { 121 | if len(args) == 1 && (args[0] == FlagHelp || args[0] == FlagHelpShort) { 122 | _ = thisCmd.Help() 123 | return nil 124 | } 125 | 126 | aliasName := args[0] 127 | 128 | aliases, err := alias.Load() 129 | if err != nil { 130 | return fmt.Errorf("error loading aliases: %w", err) 131 | } 132 | 133 | if _, exists := aliases[aliasName]; !exists { 134 | return fmt.Errorf("alias '%s' does not exist", aliasName) 135 | } 136 | 137 | delete(aliases, aliasName) 138 | 139 | if saveErr := alias.Save(aliases); saveErr != nil { 140 | return fmt.Errorf("error saving aliases: %w", saveErr) 141 | } 142 | 143 | fmt.Fprintf(thisCmd.OutOrStdout(), "Alias '%s' removed.\n", aliasName) 144 | return nil 145 | }, 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /cmd/mcptools/commands/mock.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/f/mcptools/pkg/mock" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | // MockCmd creates the mock command. 12 | func MockCmd() *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "mock [type] [name] [description] [content]...", 15 | Short: "Create a mock MCP server with tools, prompts, and resources", 16 | Long: `Create a mock MCP server with tools, prompts, and resources. 17 | This is useful for testing MCP clients without implementing a full server. 18 | 19 | The mock server implements the MCP protocol with: 20 | - Full initialization handshake (initialize method) 21 | - Support for notifications/initialized notification 22 | - Tool listing with standardized schema format 23 | - Tool calling with simple responses 24 | - Resource listing and reading with proper format 25 | - Prompt listing and retrieving with proper format 26 | - Standard error codes (-32601 for method not found) 27 | - Detailed request/response logging to ~/.mcpt/logs/mock.log 28 | 29 | Available types: 30 | - tool 31 | - prompt