├── .gitignore ├── .husky └── pre-commit ├── tsconfig.test.json ├── example ├── postman │ ├── environment.json │ └── collection.json ├── .eslintrc.json ├── tsconfig.json ├── src │ └── server.ts └── prompts │ └── CODING_STANDARDS.md ├── .github ├── dependabot.yml ├── pull_request_template.md └── workflows │ └── ci.yml ├── smithery.yaml ├── .npmignore ├── tsconfig.json ├── vitest.config.ts ├── Dockerfile ├── src ├── index.ts ├── server │ ├── types.ts │ └── server.ts └── newman │ └── runner.ts ├── LICENSE ├── .eslintrc.json ├── package.json ├── CONTRIBUTING.md ├── prompts ├── STEP_2_FIX_BROKEN_UNIT_TEST.md ├── STEP_3_EXAMPLE_API.md ├── STEP_1_INITIAL_CODE_IMPLEMENTATION.md ├── POSTMAN_NEWMAN_REFERENCE_DOCS.md └── MCP_REFERENCE_DOCS.md ├── test ├── fixtures │ └── sample-collection.json ├── newman-runner.test.ts └── server.test.ts └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | echo "Running formatter and linter before commit" 5 | pnpx lint-staged --relative 6 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "." 5 | }, 6 | "include": ["test/**/*", "vitest.config.ts"], 7 | "exclude": ["node_modules", "build"] 8 | } 9 | -------------------------------------------------------------------------------- /example/postman/environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Example API Environment", 3 | "values": [ 4 | { 5 | "key": "baseUrl", 6 | "value": "http://localhost:3000", 7 | "type": "default", 8 | "enabled": true 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | versioning-strategy: "auto" 8 | labels: 9 | - "dependencies" 10 | - "npm" 11 | commit-message: 12 | prefix: "chore" 13 | include: "scope" 14 | -------------------------------------------------------------------------------- /example/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended" 5 | ], 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "project": "./example/tsconfig.json", 9 | "ecmaVersion": 2020, 10 | "sourceType": "module" 11 | }, 12 | "plugins": [ 13 | "@typescript-eslint" 14 | ], 15 | "root": true, 16 | "ignorePatterns": ["dist/"] 17 | } 18 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | properties: {} 9 | commandFunction: 10 | # A function that produces the CLI command to start the MCP on stdio. 11 | |- 12 | (config) => ({command: 'node', args: ['build/index.js']}) 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Source files 2 | src/ 3 | example/ 4 | test/ 5 | 6 | # Configuration files 7 | .eslintrc.json 8 | tsconfig.json 9 | tsconfig.test.json 10 | vitest.config.ts 11 | 12 | # Development files 13 | .husky/ 14 | .github/ 15 | prompts/ 16 | 17 | # Documentation 18 | docs/ 19 | *.md 20 | !README.md 21 | 22 | # Git files 23 | .git/ 24 | .gitignore 25 | 26 | # IDE files 27 | .vscode/ 28 | .idea/ 29 | 30 | # Test coverage 31 | coverage/ 32 | 33 | # Lock files 34 | pnpm-lock.yaml 35 | -------------------------------------------------------------------------------- /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 | "declaration": true, 13 | "sourceMap": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "build", "test"] 17 | } 18 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | environment: 'node', 7 | include: ['test/**/*.test.ts'], 8 | coverage: { 9 | provider: 'v8', 10 | reporter: ['text', 'json', 'html'], 11 | exclude: [ 12 | 'node_modules/**', 13 | 'build/**', 14 | 'test/**', 15 | '**/*.test.ts', 16 | 'vitest.config.ts' 17 | ] 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "lib": ["ES2020"], 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "baseUrl": "..", 15 | "paths": { 16 | "*": ["node_modules/*"] 17 | } 18 | }, 19 | "include": ["src/**/*"], 20 | "exclude": ["node_modules", "dist"] 21 | } 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | # Use a Node.js image 3 | FROM node:18-alpine AS builder 4 | 5 | # Install pnpm 6 | RUN npm install -g pnpm 7 | 8 | # Set the working directory 9 | WORKDIR /app 10 | 11 | # Copy package.json and pnpm-lock.yaml to leverage Docker cache for dependencies 12 | COPY package.json pnpm-lock.yaml ./ 13 | 14 | # Install dependencies 15 | RUN pnpm install 16 | 17 | # Copy the source code 18 | COPY src ./src 19 | COPY tsconfig.json ./ 20 | 21 | # Build the project 22 | RUN pnpm build 23 | 24 | # Use a smaller Node.js image for running the application 25 | FROM node:18-alpine 26 | 27 | # Set the working directory 28 | WORKDIR /app 29 | 30 | # Copy the built files and package.json 31 | COPY --from=builder /app/build ./build 32 | COPY --from=builder /app/package.json ./ 33 | 34 | # Install only production dependencies 35 | RUN pnpm install --prod 36 | 37 | # Set the entry point for the application 38 | ENTRYPOINT ["node", "build/index.js"] 39 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { PostmanServer } from "./server/server.js"; 4 | 5 | async function main(): Promise { 6 | try { 7 | const postmanServer = new PostmanServer(); 8 | const server = await postmanServer.start(); 9 | 10 | const transport = new StdioServerTransport(); 11 | await server.connect(transport); 12 | 13 | console.error("Postman MCP Server running on stdio"); 14 | 15 | // Handle cleanup on exit 16 | process.on('SIGINT', async () => { 17 | await server.close(); 18 | process.exit(0); 19 | }); 20 | 21 | process.on('SIGTERM', async () => { 22 | await server.close(); 23 | process.exit(0); 24 | }); 25 | } catch (error) { 26 | console.error("Fatal error in main():", error); 27 | process.exit(1); 28 | } 29 | } 30 | 31 | void main(); 32 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | 5 | ## Type of Change 6 | 7 | 8 | 9 | - [ ] Bug fix (non-breaking change which fixes an issue) 10 | - [ ] New feature (non-breaking change which adds functionality) 11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - [ ] Documentation update 13 | - [ ] Other (please describe): 14 | 15 | ## Checklist 16 | 17 | 18 | 19 | - [ ] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document 20 | - [ ] My code follows the code style of this project 21 | - [ ] I have added/updated documentation as needed 22 | - [ ] I have added tests that prove my fix/feature works 23 | - [ ] All new and existing tests pass 24 | - [ ] I have tested these changes locally 25 | 26 | ## Additional Notes 27 | 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | run-name: ${{ github.actor }} triggered CI workflow 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | main: 8 | name: Lint and Test 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup PNPM 15 | uses: pnpm/action-setup@v4 16 | with: 17 | version: 8.15.4 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: "18.19.0" 23 | cache: "pnpm" 24 | cache-dependency-path: "pnpm-lock.yaml" 25 | 26 | - name: Verify lockfile 27 | run: | 28 | if [ ! -f "pnpm-lock.yaml" ]; then 29 | echo "pnpm-lock.yaml not found!" 30 | exit 1 31 | fi 32 | echo "pnpm-lock.yaml found" 33 | cat pnpm-lock.yaml 34 | 35 | - name: Install dependencies 36 | run: pnpm install 37 | 38 | - name: Run ESLint 39 | run: pnpm lint 40 | 41 | - name: Run Tests 42 | run: pnpm test:ci 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 shannonlal 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. 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es2022": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 10 | "prettier" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": "latest", 15 | "sourceType": "module", 16 | "project": ["./tsconfig.json", "./tsconfig.test.json"] 17 | }, 18 | "plugins": ["@typescript-eslint"], 19 | "rules": { 20 | "@typescript-eslint/explicit-function-return-type": "error", 21 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 22 | "@typescript-eslint/no-explicit-any": "error", 23 | "@typescript-eslint/no-floating-promises": "error", 24 | "@typescript-eslint/no-misused-promises": ["error", { 25 | "checksVoidReturn": { 26 | "arguments": false 27 | } 28 | }], 29 | "no-console": ["error", { "allow": ["warn", "error"] }], 30 | "@typescript-eslint/require-await": "warn" 31 | }, 32 | "ignorePatterns": ["build/", "coverage/", "*.js"] 33 | } 34 | -------------------------------------------------------------------------------- /src/server/types.ts: -------------------------------------------------------------------------------- 1 | // Types for Newman runner input 2 | export interface CollectionRunOptions { 3 | collection: string; // Path or URL to Postman collection 4 | environment?: string; // Optional path or URL to environment file 5 | globals?: string; // Optional path or URL to globals file 6 | iterationCount?: number; // Optional number of iterations to run 7 | } 8 | 9 | // Types for test results 10 | export interface TestSummary { 11 | total: number; 12 | failed: number; 13 | passed: number; 14 | } 15 | 16 | export interface TestFailure { 17 | name: string; 18 | error: string; 19 | request: { 20 | method: string; 21 | url: string; 22 | }; 23 | } 24 | 25 | export interface TestTimings { 26 | started: string; 27 | completed: string; 28 | duration: number; 29 | } 30 | 31 | export interface TestResult { 32 | success: boolean; 33 | summary: TestSummary; 34 | failures: TestFailure[]; 35 | timings: TestTimings; 36 | } 37 | 38 | // MCP Tool response type 39 | export interface McpToolResponse { 40 | content: [{ 41 | type: "text"; 42 | text: string; 43 | }]; 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcp-postman", 3 | "version": "1.0.2", 4 | "description": "MCP Server for running Postman collections using Newman", 5 | "main": "build/index.js", 6 | "type": "module", 7 | "bin": { 8 | "mcp-postman": "./build/index.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc && chmod +x build/index.js", 12 | "test": "vitest", 13 | "test:ci": "vitest run", 14 | "test:coverage": "vitest run --coverage", 15 | "lint": "eslint . --ext .ts", 16 | "format": "prettier --write \"src/**/*.ts\"", 17 | "clean": "rm -rf build", 18 | "prepare": "husky", 19 | "example:dev": "cross-env NODE_OPTIONS=\"--loader ts-node/esm\" ts-node --watch example/src/server.ts", 20 | "example:nodemon": "cross-env NODE_OPTIONS=\"--loader ts-node/esm\" nodemon --watch example example/src/server.ts", 21 | "example:build": "tsc -p example/tsconfig.json" 22 | }, 23 | "lint-staged": { 24 | "*.ts": [ 25 | "prettier --write", 26 | "eslint --fix", 27 | "vitest related --run" 28 | ] 29 | }, 30 | "keywords": [ 31 | "mcp", 32 | "postman", 33 | "newman", 34 | "api-testing" 35 | ], 36 | "author": "", 37 | "license": "ISC", 38 | "dependencies": { 39 | "@modelcontextprotocol/sdk": "latest", 40 | "newman": "^6.0.0", 41 | "zod": "^3.22.4" 42 | }, 43 | "devDependencies": { 44 | "@types/express": "^4.17.21", 45 | "@types/newman": "^5.3.0", 46 | "@types/node": "^20.0.0", 47 | "@typescript-eslint/eslint-plugin": "^6.0.0", 48 | "@typescript-eslint/parser": "^6.0.0", 49 | "@vitest/coverage-v8": "^1.0.0", 50 | "axios": "^1.7.9", 51 | "cross-env": "^7.0.3", 52 | "eslint": "^8.0.0", 53 | "eslint-config-prettier": "^9.0.0", 54 | "express": "^4.18.2", 55 | "husky": "^9.1.7", 56 | "lint-staged": "^15.3.0", 57 | "nodemon":"3.1.9", 58 | "prettier": "^3.0.0", 59 | "ts-node": "^10.9.0", 60 | "typescript": "^5.0.0", 61 | "vitest": "^1.0.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to MCP Postman Server 2 | 3 | We love your input! We want to make contributing to MCP Postman Server as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## We Develop with GitHub 12 | 13 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. 14 | 15 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html) 16 | 17 | Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests: 18 | 19 | 1. Fork the repo and create your branch from `main`. 20 | 2. If you've added code that should be tested, add tests. 21 | 3. If you've changed APIs, update the documentation. 22 | 4. Ensure the test suite passes. 23 | 5. Make sure your code lints. 24 | 6. Issue that pull request! 25 | 26 | ## Any contributions you make will be under the MIT Software License 27 | 28 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 29 | 30 | ## Report bugs using GitHub's issue tracker 31 | 32 | We use GitHub issues to track public bugs. Report a bug by opening a new issue; it's that easy! 33 | 34 | ## Write bug reports with detail, background, and sample code 35 | 36 | **Great Bug Reports** tend to have: 37 | 38 | - A quick summary and/or background 39 | - Steps to reproduce 40 | - Be specific! 41 | - Give sample code if you can. 42 | - What you expected would happen 43 | - What actually happens 44 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 45 | 46 | ## Use a Consistent Coding Style 47 | 48 | - Use 2 spaces for indentation rather than tabs 49 | - Use TypeScript types and interfaces 50 | - Keep line length to 100 characters or less 51 | - Write JSDoc comments for functions and classes 52 | - Follow the project's ESLint configuration 53 | 54 | ## License 55 | 56 | By contributing, you agree that your contributions will be licensed under its MIT License. 57 | -------------------------------------------------------------------------------- /prompts/STEP_2_FIX_BROKEN_UNIT_TEST.md: -------------------------------------------------------------------------------- 1 | After analyzing both the server implementation and test file, here are the key test scenarios that should be covered for the PostmanServer class: 2 | 3 | Server Initialization Tests: 4 | 5 | Server should be properly initialized with correct name and version 6 | NewmanRunner should be instantiated 7 | Tools should be properly registered during setup 8 | Tool Listing Tests: 9 | 10 | Should list exactly one tool named "run-collection" 11 | Tool description should be present 12 | Input schema should correctly specify required and optional parameters: 13 | collection (required, string) 14 | environment (optional, string) 15 | globals (optional, string) 16 | iterationCount (optional, number) 17 | Collection Running Tests: 18 | 19 | Successful collection run with minimal parameters (just collection path) 20 | Successful collection run with all optional parameters 21 | Error handling for invalid collection path 22 | Error handling for invalid environment file 23 | Error handling for invalid globals file 24 | Error handling for invalid iterationCount (less than 1) 25 | 26 | Input Validation Tests: 27 | 28 | Should reject when collection parameter is missing 29 | Should reject when iterationCount is less than 1 30 | Should reject when unknown tool name is provided 31 | Should handle invalid input types (e.g., number for collection path) 32 | Response Format Tests: 33 | 34 | Successful response should have correct structure with content array 35 | Error response should have isError flag and proper error message 36 | Response should properly stringify Newman runner results 37 | Response should maintain proper JSON-RPC 2.0 format 38 | Looking at the current test file: 39 | 40 | Most tests are commented out 41 | The only active test is a basic initialization test 42 | The mock transport setup is commented out, which is needed for proper MCP server testing 43 | The NewmanRunner mock is set up but not fully utilized 44 | The test file needs to be fixed by: 45 | 46 | Implementing proper transport mocking 47 | Uncommenting and fixing the existing tests 48 | Adding missing test cases for complete coverage 49 | Ensuring proper error handling tests 50 | Adding validation for all optional parameters 51 | Would you like me to proceed with implementing these test cases? 52 | -------------------------------------------------------------------------------- /prompts/STEP_3_EXAMPLE_API.md: -------------------------------------------------------------------------------- 1 | Implementation Plan for Sample Express Server with Postman Tests: 2 | 3 | Dependencies Required (devDependencies): 4 | { 5 | "devDependencies": { 6 | "@types/express": "^4.17.x", 7 | "@types/node": "^20.x.x", 8 | "express": "^4.18.x", 9 | "typescript": "^5.x.x" 10 | } 11 | } 12 | Example Directory Structure: 13 | example/ 14 | ├── src/ 15 | │ └── server.ts # Express server implementation 16 | ├── postman/ 17 | │ ├── collection.json # Postman collection file 18 | │ └── environment.json # Postman environment file 19 | ├── package.json # Project dependencies 20 | └── tsconfig.json # TypeScript configuration 21 | Server Implementation Plan (server.ts): 22 | Create an Express server with two endpoints: 23 | GET /api/items - Returns a list of sample items 24 | POST /api/items - Accepts a new item and returns the created item 25 | Server will run on port 3000 26 | Include proper TypeScript types for request/response objects 27 | Implement basic error handling 28 | Add request logging middleware 29 | Postman Environment (environment.json): 30 | { 31 | "name": "Sample API Environment", 32 | "values": [ 33 | { 34 | "key": "baseUrl", 35 | "value": "http://localhost:3000", 36 | "type": "default", 37 | "enabled": true 38 | } 39 | ] 40 | } 41 | Postman Collection Plan (collection.json): 42 | Collection Name: "Sample API Tests" 43 | Two request folders: 44 | GET Requests 45 | Test endpoint: GET {{baseUrl}}/api/items 46 | Tests to verify: 47 | Status code is 200 48 | Response is an array 49 | Response time is acceptable 50 | POST Requests 51 | Test endpoint: POST {{baseUrl}}/api/items 52 | Request body: JSON object with item details 53 | Tests to verify: 54 | Status code is 201 55 | Response contains created item 56 | Response matches request schema 57 | Additional Considerations: 58 | 59 | Error Handling: 60 | 61 | Implement proper HTTP status codes 62 | Return meaningful error messages 63 | Add request validation 64 | TypeScript Types: 65 | 66 | Define interfaces for request/response objects 67 | Use proper type annotations 68 | Ensure strict type checking 69 | Testing Strategy: 70 | 71 | Collection will include pre-request scripts 72 | Environment variables for configuration 73 | Test assertions for each endpoint 74 | Response schema validation 75 | Documentation: 76 | 77 | Add API documentation in collection 78 | Include example requests/responses 79 | Document environment setup 80 | This implementation plan provides a structured approach to creating a sample Express server with Postman tests while maintaining TypeScript best practices and proper testing coverage. 81 | -------------------------------------------------------------------------------- /example/src/server.ts: -------------------------------------------------------------------------------- 1 | import express, { 2 | type Request, 3 | type Response, 4 | type NextFunction, 5 | } from "express"; 6 | import axios from "axios"; 7 | 8 | // Types for CoinDesk API 9 | interface CoinDeskTime { 10 | updated: string; 11 | updatedISO: string; 12 | updateduk: string; 13 | } 14 | 15 | interface CoinDeskCurrency { 16 | code: string; 17 | symbol: string; 18 | rate: string; 19 | description: string; 20 | rate_float: number; 21 | } 22 | 23 | interface CoinDeskBPI { 24 | USD: CoinDeskCurrency; 25 | GBP: CoinDeskCurrency; 26 | EUR: CoinDeskCurrency; 27 | } 28 | 29 | interface CoinDeskResponse { 30 | time: CoinDeskTime; 31 | disclaimer: string; 32 | chartName: string; 33 | bpi: CoinDeskBPI; 34 | } 35 | 36 | // Types for our API 37 | interface Item { 38 | id: string; 39 | name: string; 40 | description: string; 41 | createdAt: string; 42 | } 43 | 44 | interface CreateItemRequest { 45 | name: string; 46 | description: string; 47 | } 48 | 49 | // In-memory storage for items 50 | const items: Item[] = []; 51 | 52 | const app = express(); 53 | const PORT = 3000; 54 | 55 | // Middleware 56 | app.use(express.json()); 57 | app.use((req: Request, _res: Response, next: NextFunction) => { 58 | console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`); 59 | next(); 60 | }); 61 | 62 | // GET /api/items - Get all items 63 | app.get("/api/items", (_req: Request, res: Response) => { 64 | res.json(items); 65 | }); 66 | 67 | // POST /api/items - Create a new item 68 | app.post("/api/items", (req: Request, res: Response) => { 69 | const { name, description } = req.body as CreateItemRequest; 70 | 71 | // Validate request body 72 | if (!name || !description) { 73 | return res.status(400).json({ error: "Name and description are required" }); 74 | } 75 | 76 | const newItem: Item = { 77 | id: Date.now().toString(), 78 | name, 79 | description, 80 | createdAt: new Date().toISOString(), 81 | }; 82 | 83 | items.push(newItem); 84 | res.status(201).json(newItem); 85 | }); 86 | 87 | // Start server 88 | app.listen(PORT, () => { 89 | console.log(`Server is running on http://localhost:${PORT}`); 90 | }); 91 | 92 | // GET /api/currentprice - Get current Bitcoin price 93 | app.get("/api/currentprice", async (_req: Request, res: Response) => { 94 | try { 95 | const response = await axios.get( 96 | "https://api.coindesk.com/v1/bpi/currentprice.json", 97 | ); 98 | res.status(200).json(response.data); 99 | } catch (error) { 100 | res.status(500).json({ error: "Failed to fetch Bitcoin price data" }); 101 | } 102 | }); 103 | 104 | // Error handling middleware 105 | app.use((err: Error, _req: Request, res: Response) => { 106 | console.error(err.stack); 107 | res.status(500).json({ error: "Something went wrong!" }); 108 | }); 109 | 110 | export default app; 111 | -------------------------------------------------------------------------------- /src/newman/runner.ts: -------------------------------------------------------------------------------- 1 | import newman, { NewmanRunFailure } from 'newman'; 2 | import { CollectionRunOptions, TestResult, TestFailure } from '../server/types.js'; 3 | 4 | /** 5 | * Safely extracts test failure information from a Newman failure object 6 | */ 7 | function extractFailureInfo(failure: NewmanRunFailure): TestFailure | null { 8 | try { 9 | if (!failure.error || !failure.source?.request) { 10 | return null; 11 | } 12 | 13 | const { error, source } = failure; 14 | const { request } = source; 15 | 16 | // Ensure we have all required properties 17 | if (!error.test || !error.message || !request.method || !request.url) { 18 | return null; 19 | } 20 | 21 | return { 22 | name: error.test, 23 | error: error.message, 24 | request: { 25 | method: request.method, 26 | url: request.url.toString() 27 | } 28 | }; 29 | } catch { 30 | return null; 31 | } 32 | } 33 | 34 | export class NewmanRunner { 35 | /** 36 | * Runs a Postman collection using Newman 37 | * @param options Collection run options 38 | * @returns Test results 39 | */ 40 | async runCollection(options: CollectionRunOptions): Promise { 41 | return new Promise((resolve, reject) => { 42 | const startTime = new Date().toISOString(); 43 | 44 | newman.run({ 45 | collection: options.collection, 46 | environment: options.environment, 47 | globals: options.globals, 48 | iterationCount: options.iterationCount, 49 | reporters: 'cli' 50 | }, (err, summary) => { 51 | if (err) { 52 | reject(err); 53 | return; 54 | } 55 | 56 | const endTime = new Date().toISOString(); 57 | 58 | // Format the results 59 | const result: TestResult = { 60 | success: summary.run.failures.length === 0, 61 | summary: { 62 | total: summary.run.stats.tests.total || 0, 63 | failed: summary.run.stats.tests.failed || 0, 64 | passed: (summary.run.stats.tests.total || 0) - (summary.run.stats.tests.failed || 0) 65 | }, 66 | failures: (summary.run.failures || []) 67 | .map(extractFailureInfo) 68 | .filter((failure): failure is TestFailure => failure !== null), 69 | timings: { 70 | started: startTime, 71 | completed: endTime, 72 | duration: new Date(endTime).getTime() - new Date(startTime).getTime() 73 | } 74 | }; 75 | 76 | resolve(result); 77 | }); 78 | }); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/fixtures/sample-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "Sample API Tests", 4 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 5 | }, 6 | "item": [ 7 | { 8 | "name": "Get User", 9 | "event": [ 10 | { 11 | "listen": "test", 12 | "script": { 13 | "exec": [ 14 | "pm.test('Status code is 200', function () {", 15 | " pm.response.to.have.status(200);", 16 | "});", 17 | "", 18 | "pm.test('Response has user data', function () {", 19 | " const responseJson = pm.response.json();", 20 | " pm.expect(responseJson).to.have.property('id');", 21 | " pm.expect(responseJson).to.have.property('name');", 22 | "});" 23 | ], 24 | "type": "text/javascript" 25 | } 26 | } 27 | ], 28 | "request": { 29 | "method": "GET", 30 | "header": [], 31 | "url": { 32 | "raw": "https://jsonplaceholder.typicode.com/users/1", 33 | "protocol": "https", 34 | "host": [ 35 | "jsonplaceholder", 36 | "typicode", 37 | "com" 38 | ], 39 | "path": [ 40 | "users", 41 | "1" 42 | ] 43 | } 44 | } 45 | }, 46 | { 47 | "name": "Create User", 48 | "event": [ 49 | { 50 | "listen": "test", 51 | "script": { 52 | "exec": [ 53 | "pm.test('Status code is 201', function () {", 54 | " pm.response.to.have.status(201);", 55 | "});", 56 | "", 57 | "pm.test('Response has created user data', function () {", 58 | " const responseJson = pm.response.json();", 59 | " pm.expect(responseJson).to.have.property('id');", 60 | " pm.expect(responseJson.name).to.eql('John Doe');", 61 | "});" 62 | ], 63 | "type": "text/javascript" 64 | } 65 | } 66 | ], 67 | "request": { 68 | "method": "POST", 69 | "header": [ 70 | { 71 | "key": "Content-Type", 72 | "value": "application/json" 73 | } 74 | ], 75 | "body": { 76 | "mode": "raw", 77 | "raw": "{\n \"name\": \"John Doe\",\n \"email\": \"john@example.com\"\n}" 78 | }, 79 | "url": { 80 | "raw": "https://jsonplaceholder.typicode.com/users", 81 | "protocol": "https", 82 | "host": [ 83 | "jsonplaceholder", 84 | "typicode", 85 | "com" 86 | ], 87 | "path": [ 88 | "users" 89 | ] 90 | } 91 | } 92 | } 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /prompts/STEP_1_INITIAL_CODE_IMPLEMENTATION.md: -------------------------------------------------------------------------------- 1 | Detailed Implementation Plan for Postman MCP Server 2 | 3 | 1. Project Setup & Dependencies 4 | Regular Dependencies 5 | { 6 | "dependencies": { 7 | "@modelcontextprotocol/sdk": "^latest", 8 | "newman": "^latest", 9 | "zod": "^latest" 10 | } 11 | } 12 | Dev Dependencies 13 | { 14 | "devDependencies": { 15 | "typescript": "^latest", 16 | "vitest": "^latest", 17 | "@types/node": "^latest", 18 | "@types/newman": "^latest", 19 | "ts-node": "^latest", 20 | "prettier": "^latest", 21 | "eslint": "^latest", 22 | "eslint-config-prettier": "^latest" 23 | } 24 | } 25 | 2. Project Structure 26 | /postman-mcp-server 27 | ├── package.json 28 | ├── tsconfig.json 29 | ├── vitest.config.ts 30 | ├── src/ 31 | │ ├── index.ts # Main entry point 32 | │ ├── server/ 33 | │ │ ├── server.ts # MCP Server implementation 34 | │ │ └── types.ts # Type definitions 35 | │ ├── newman/ 36 | │ │ ├── runner.ts # Newman runner implementation 37 | │ │ └── types.ts # Newman types 38 | │ └── utils/ 39 | │ └── result-formatter.ts # Format Newman results 40 | └── test/ 41 | ├── server.test.ts # Server tests 42 | ├── newman-runner.test.ts # Newman runner tests 43 | └── fixtures/ # Test fixtures 44 | ├── sample-collection.json 45 | └── sample-environment.json 46 | 3. Key File Implementation Plans 47 | src/index.ts 48 | // Main entry point 49 | // - Initialize MCP Server 50 | // - Connect transport 51 | // - Handle errors 52 | src/server/types.ts 53 | // Define interfaces for: 54 | interface CollectionRunRequest { 55 | collection: string; 56 | environment?: string; 57 | globals?: string; 58 | iterationCount?: number; 59 | } 60 | 61 | interface TestResult { 62 | success: boolean; 63 | summary: TestSummary; 64 | failures: TestFailure[]; 65 | timings: TestTimings; 66 | } 67 | src/server/server.ts 68 | // Implement: 69 | // 1. MCP Server setup 70 | // 2. Tool registration 71 | // 3. Request handling 72 | // 4. Error handling 73 | src/newman/runner.ts 74 | // Implement: 75 | // 1. Newman run configuration 76 | // 2. Result collection 77 | // 3. Error handling 78 | // 4. Resource cleanup 79 | src/utils/result-formatter.ts 80 | // Implement: 81 | // 1. Newman result parsing 82 | // 2. MCP response formatting 83 | // 3. Error formatting 4. Testing Strategy 84 | Unit Tests (vitest.config.ts) 85 | // Configure: 86 | // - Test environment 87 | // - Coverage reporting 88 | // - Test matching patterns 89 | Test Files 90 | test/server.test.ts 91 | // Test cases for: 92 | // 1. Server initialization 93 | // 2. Tool registration 94 | // 3. Request handling 95 | // 4. Error scenarios 96 | test/newman-runner.test.ts 97 | // Test cases for: 98 | // 1. Collection running 99 | // 2. Result parsing 100 | // 3. Error handling 101 | // 4. Resource cleanup 5. Configuration Files 102 | tsconfig.json 103 | { 104 | "compilerOptions": { 105 | "target": "ES2022", 106 | "module": "Node16", 107 | "outDir": "./build", 108 | "strict": true, 109 | "esModuleInterop": true 110 | } 111 | } 6. Implementation Phases 112 | Phase 1: Basic Setup 113 | 114 | Project structure creation 115 | Dependency installation 116 | Configuration files 117 | Phase 2: Core Implementation 118 | 119 | Newman runner implementation 120 | Result formatter 121 | Basic error handling 122 | Phase 3: MCP Server 123 | 124 | Server setup 125 | Tool registration 126 | Request handling 127 | Phase 4: Testing 128 | 129 | Unit test implementation 130 | Integration tests 131 | Test fixtures 132 | Phase 5: Documentation & Polish 133 | 134 | API documentation 135 | Usage examples 136 | Error handling improvements 7. Testing Scenarios 137 | Server Tests 138 | 139 | Server initialization 140 | Tool registration 141 | Request validation 142 | Error handling 143 | Newman Runner Tests 144 | 145 | Collection execution 146 | Environment handling 147 | Result parsing 148 | Error scenarios 149 | Integration Tests 150 | -------------------------------------------------------------------------------- /example/postman/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "Example API Tests", 4 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 5 | }, 6 | "item": [ 7 | { 8 | "name": "GET Items", 9 | "request": { 10 | "method": "GET", 11 | "header": [], 12 | "url": { 13 | "raw": "{{baseUrl}}/api/items", 14 | "host": ["{{baseUrl}}"], 15 | "path": ["api", "items"] 16 | } 17 | }, 18 | "event": [ 19 | { 20 | "listen": "test", 21 | "script": { 22 | "exec": [ 23 | "pm.test('Status code is 200', function () {", 24 | " pm.response.to.have.status(200);", 25 | "});", 26 | "", 27 | "pm.test('Response is an array', function () {", 28 | " const responseData = pm.response.json();", 29 | " pm.expect(Array.isArray(responseData)).to.be.true;", 30 | "});", 31 | "", 32 | "pm.test('Response time is acceptable', function () {", 33 | " pm.expect(pm.response.responseTime).to.be.below(1000);", 34 | "});" 35 | ], 36 | "type": "text/javascript" 37 | } 38 | } 39 | ] 40 | }, 41 | { 42 | "name": "Create Item", 43 | "request": { 44 | "method": "POST", 45 | "header": [ 46 | { 47 | "key": "Content-Type", 48 | "value": "application/json" 49 | } 50 | ], 51 | "body": { 52 | "mode": "raw", 53 | "raw": "{\n \"name\": \"Test Item\",\n \"description\": \"This is a test item\"\n}" 54 | }, 55 | "url": { 56 | "raw": "{{baseUrl}}/api/items", 57 | "host": ["{{baseUrl}}"], 58 | "path": ["api", "items"] 59 | } 60 | }, 61 | "event": [ 62 | { 63 | "listen": "test", 64 | "script": { 65 | "exec": [ 66 | "pm.test('Status code is 201', function () {", 67 | " pm.response.to.have.status(201);", 68 | "});", 69 | "", 70 | "pm.test('Response has correct structure', function () {", 71 | " const responseData = pm.response.json();", 72 | " pm.expect(responseData).to.have.property('id');", 73 | " pm.expect(responseData).to.have.property('name');", 74 | " pm.expect(responseData).to.have.property('description');", 75 | " pm.expect(responseData).to.have.property('createdAt');", 76 | "});", 77 | "", 78 | "pm.test('Created item matches request', function () {", 79 | " const responseData = pm.response.json();", 80 | " const requestData = JSON.parse(pm.request.body.raw);", 81 | " pm.expect(responseData.name).to.equal(requestData.name);", 82 | " pm.expect(responseData.description).to.equal(requestData.description);", 83 | "});" 84 | ], 85 | "type": "text/javascript" 86 | } 87 | } 88 | ] 89 | }, 90 | { 91 | "name": "Get Current Bitcoin Price", 92 | "request": { 93 | "method": "GET", 94 | "header": [], 95 | "url": { 96 | "raw": "{{baseUrl}}/api/currentprice", 97 | "host": ["{{baseUrl}}"], 98 | "path": ["api", "currentprice"] 99 | } 100 | }, 101 | "event": [ 102 | { 103 | "listen": "test", 104 | "script": { 105 | "exec": [ 106 | "pm.test(\"Status code is 200\", function () {", 107 | " pm.response.to.have.status(200);", 108 | "});" 109 | ], 110 | "type": "text/javascript" 111 | } 112 | } 113 | ] 114 | } 115 | ] 116 | } 117 | -------------------------------------------------------------------------------- /src/server/server.ts: -------------------------------------------------------------------------------- 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 2 | import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; 3 | import { z } from "zod"; 4 | import { NewmanRunner } from "../newman/runner.js"; 5 | 6 | // Input validation schema 7 | const RunCollectionSchema = z.object({ 8 | collection: z.string(), 9 | environment: z.string().optional(), 10 | globals: z.string().optional(), 11 | iterationCount: z.number().min(1).optional() 12 | }); 13 | 14 | export class PostmanServer { 15 | private server: Server; 16 | private runner: NewmanRunner; 17 | 18 | constructor() { 19 | this.server = new Server( 20 | { 21 | name: "postman-runner", 22 | version: "1.0.0", 23 | }, 24 | { 25 | capabilities: { 26 | tools: {}, 27 | }, 28 | } 29 | ); 30 | 31 | this.runner = new NewmanRunner(); 32 | this.setupTools(); 33 | } 34 | 35 | private setupTools(): void { 36 | // Register available tools 37 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 38 | tools: [ 39 | { 40 | name: "run-collection", 41 | description: "Run a Postman Collection using Newman", 42 | inputSchema: { 43 | type: "object", 44 | properties: { 45 | collection: { 46 | type: "string", 47 | description: "Path or URL to the Postman collection" 48 | }, 49 | environment: { 50 | type: "string", 51 | description: "Optional path or URL to environment file" 52 | }, 53 | globals: { 54 | type: "string", 55 | description: "Optional path or URL to globals file" 56 | }, 57 | iterationCount: { 58 | type: "number", 59 | description: "Optional number of iterations to run" 60 | } 61 | }, 62 | required: ["collection"] 63 | } 64 | } 65 | ] 66 | })); 67 | 68 | // Handle tool execution 69 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 70 | if (request.params.name !== "run-collection") { 71 | throw new Error(`Unknown tool: ${request.params.name}`); 72 | } 73 | 74 | // Validate input 75 | const args = RunCollectionSchema.parse(request.params.arguments); 76 | 77 | try { 78 | // Run the collection 79 | const result = await this.runner.runCollection(args); 80 | 81 | // Format the response 82 | return { 83 | content: [{ 84 | type: "text", 85 | text: JSON.stringify(result, null, 2) 86 | }] 87 | }; 88 | } catch (error) { 89 | const errorMessage = error instanceof Error ? error.message : String(error); 90 | return { 91 | content: [{ 92 | type: "text", 93 | text: JSON.stringify({ 94 | error: errorMessage, 95 | success: false 96 | }, null, 2) 97 | }], 98 | isError: true 99 | }; 100 | } 101 | }); 102 | } 103 | 104 | async start(): Promise { 105 | // This will be connected in index.ts 106 | return Promise.resolve(this.server); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Postman MCP Server 2 | [![smithery badge](https://smithery.ai/badge/mcp-postman)](https://smithery.ai/server/mcp-postman) 3 | 4 | An MCP (Model Context Protocol) server that enables running Postman collections using Newman. This server allows LLMs to execute API tests and get detailed results through a standardized interface. 5 | 6 | [![MCP Postman Server Demo](https://img.youtube.com/vi/d1WgTqwMsog/0.jpg)](https://youtu.be/d1WgTqwMsog) 7 | 8 | Postman Server MCP server 9 | 10 | ## Features 11 | 12 | - Run Postman collections using Newman 13 | - Support for environment files 14 | - Support for global variables 15 | - Detailed test results including: 16 | - Overall success/failure status 17 | - Test summary (total, passed, failed) 18 | - Detailed failure information 19 | - Execution timings 20 | 21 | ## Installation 22 | 23 | ### Installing via Smithery 24 | 25 | To install Postman Runner for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mcp-postman): 26 | 27 | ```bash 28 | npx -y @smithery/cli install mcp-postman --client claude 29 | ``` 30 | 31 | ### Manual Installation 32 | ```bash 33 | # Clone the repository 34 | git clone 35 | cd mcp-postman 36 | 37 | # Install dependencies 38 | pnpm install 39 | 40 | # Build the project 41 | pnpm build 42 | ``` 43 | 44 | ## Usage 45 | 46 | ### Configuration 47 | 48 | Add the server to your Claude desktop configuration file at `~/Library/Application Support/Claude/claude_desktop_config.json`: 49 | 50 | ```json 51 | { 52 | "mcpServers": { 53 | "postman-runner": { 54 | "command": "node", 55 | "args": ["/absolute/path/to/mcp-postman/build/index.js"] 56 | } 57 | } 58 | } 59 | ``` 60 | 61 | ### Available Tools 62 | 63 | #### run-collection 64 | 65 | Runs a Postman collection and returns the test results. 66 | 67 | **Parameters:** 68 | 69 | - `collection` (required): Path or URL to the Postman collection 70 | - `environment` (optional): Path or URL to environment file 71 | - `globals` (optional): Path or URL to globals file 72 | - `iterationCount` (optional): Number of iterations to run 73 | 74 | **Example Response:** 75 | 76 | ```json 77 | { 78 | "success": true, 79 | "summary": { 80 | "total": 5, 81 | "failed": 0, 82 | "passed": 5 83 | }, 84 | "failures": [], 85 | "timings": { 86 | "started": "2024-03-14T10:00:00.000Z", 87 | "completed": "2024-03-14T10:00:01.000Z", 88 | "duration": 1000 89 | } 90 | } 91 | ``` 92 | 93 | ### Example Usage in Claude 94 | 95 | You can use the server in Claude by asking it to run a Postman collection: 96 | 97 | "Run the Postman collection at /path/to/collection.json and tell me if all tests passed" 98 | 99 | Claude will: 100 | 101 | 1. Use the run-collection tool 102 | 2. Analyze the test results 103 | 3. Provide a human-friendly summary of the execution 104 | 105 | ## Development 106 | 107 | ### Project Structure 108 | 109 | ``` 110 | src/ 111 | ├── index.ts # Entry point 112 | ├── server/ 113 | │ ├── server.ts # MCP Server implementation 114 | │ └── types.ts # Type definitions 115 | └── newman/ 116 | └── runner.ts # Newman runner implementation 117 | test/ 118 | ├── server.test.ts # Server tests 119 | ├── newman-runner.test.ts # Runner tests 120 | └── fixtures/ # Test fixtures 121 | └── sample-collection.json 122 | ``` 123 | 124 | ### Running Tests 125 | 126 | ```bash 127 | # Run tests 128 | pnpm test 129 | 130 | # Run tests with coverage 131 | pnpm test:coverage 132 | ``` 133 | 134 | ### Building 135 | 136 | ```bash 137 | # Build the project 138 | pnpm build 139 | 140 | # Clean build artifacts 141 | pnpm clean 142 | ``` 143 | 144 | ## Contributing 145 | 146 | 1. Fork the repository 147 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 148 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 149 | 4. Push to the branch (`git push origin feature/amazing-feature`) 150 | 5. Open a Pull Request 151 | 152 | ## License 153 | 154 | ISC 155 | -------------------------------------------------------------------------------- /test/newman-runner.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from "vitest"; 2 | import { NewmanRunner } from "../src/newman/runner.js"; 3 | import type { NewmanRunSummary, NewmanRunOptions } from "newman"; 4 | import { EventEmitter } from "events"; 5 | 6 | const mockRun = vi.hoisted(() => vi.fn()); 7 | 8 | vi.mock("newman", () => ({ 9 | default: { 10 | run: mockRun, 11 | }, 12 | })); 13 | 14 | describe("NewmanRunner", () => { 15 | beforeEach(() => { 16 | mockRun.mockReset(); 17 | mockRun.mockImplementation( 18 | ( 19 | options: NewmanRunOptions, 20 | callback: (err: Error | null, summary: NewmanRunSummary) => void, 21 | ) => { 22 | callback(null, {} as NewmanRunSummary); 23 | return new EventEmitter(); 24 | }, 25 | ); 26 | }); 27 | 28 | const runner = new NewmanRunner(); 29 | 30 | it("should successfully run a collection", async (): Promise => { 31 | // Mock successful newman run 32 | const mockSummary = { 33 | run: { 34 | stats: { 35 | tests: { 36 | total: 5, 37 | failed: 1, 38 | }, 39 | }, 40 | failures: [ 41 | { 42 | error: { 43 | test: "Test case 1", 44 | message: "Expected 200 but got 404", 45 | }, 46 | source: { 47 | request: { 48 | method: "GET", 49 | url: { 50 | toString: () => "https://api.example.com/test", 51 | }, 52 | }, 53 | }, 54 | }, 55 | ], 56 | }, 57 | }; 58 | 59 | mockRun.mockImplementationOnce( 60 | ( 61 | options: NewmanRunOptions, 62 | callback: (err: Error | null, summary: NewmanRunSummary) => void, 63 | ) => { 64 | if (typeof callback === "function") { 65 | callback(null, mockSummary as unknown as NewmanRunSummary); 66 | } 67 | return new EventEmitter(); 68 | }, 69 | ); 70 | 71 | const result = await runner.runCollection({ 72 | collection: "./test-collection.json", 73 | }); 74 | 75 | expect(result.success).toBe(false); 76 | expect(result.summary.total).toBe(5); 77 | expect(result.summary.failed).toBe(1); 78 | expect(result.summary.passed).toBe(4); 79 | expect(result.failures).toHaveLength(1); 80 | expect(result.failures[0]).toEqual({ 81 | name: "Test case 1", 82 | error: "Expected 200 but got 404", 83 | request: { 84 | method: "GET", 85 | url: "https://api.example.com/test", 86 | }, 87 | }); 88 | }); 89 | 90 | it("should handle newman run errors", async (): Promise => { 91 | // Mock newman error 92 | mockRun.mockImplementationOnce( 93 | ( 94 | options: NewmanRunOptions, 95 | callback: (err: Error | null, summary: NewmanRunSummary) => void, 96 | ) => { 97 | if (typeof callback === "function") { 98 | callback( 99 | new Error("Failed to load collection"), 100 | {} as NewmanRunSummary, 101 | ); 102 | } 103 | return new EventEmitter(); 104 | }, 105 | ); 106 | 107 | await expect( 108 | runner.runCollection({ 109 | collection: "./invalid-collection.json", 110 | }), 111 | ).rejects.toThrow("Failed to load collection"); 112 | }); 113 | 114 | it("should handle invalid failure objects", async (): Promise => { 115 | // Mock newman run with invalid failure object 116 | const mockSummary = { 117 | run: { 118 | stats: { 119 | tests: { 120 | total: 1, 121 | failed: 1, 122 | }, 123 | }, 124 | failures: [ 125 | { 126 | // Missing required properties 127 | error: {}, 128 | source: {}, 129 | }, 130 | ], 131 | }, 132 | }; 133 | 134 | mockRun.mockImplementationOnce( 135 | ( 136 | options: NewmanRunOptions, 137 | callback: (err: Error | null, summary: NewmanRunSummary) => void, 138 | ) => { 139 | if (typeof callback === "function") { 140 | callback(null, mockSummary as unknown as NewmanRunSummary); 141 | } 142 | return new EventEmitter(); 143 | }, 144 | ); 145 | 146 | const result = await runner.runCollection({ 147 | collection: "./test-collection.json", 148 | }); 149 | 150 | expect(result.success).toBe(false); 151 | expect(result.failures).toHaveLength(0); // Invalid failure should be filtered out 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /test/server.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from "vitest"; 2 | 3 | // Define types for collection run parameters 4 | interface CollectionRunParams { 5 | collection: string; 6 | environment?: string; 7 | globals?: string; 8 | iterationCount?: number; 9 | } 10 | 11 | vi.mock("../src/newman/runner.js", () => ({ 12 | NewmanRunner: vi.fn().mockImplementation(() => ({ 13 | runCollection: vi.fn(), 14 | })), 15 | })); 16 | 17 | import { PostmanServer } from "../src/server/server.js"; 18 | import { NewmanRunner } from "../src/newman/runner.js"; 19 | 20 | const MockedNewmanRunner = vi.mocked(NewmanRunner); 21 | 22 | describe("PostmanServer", () => { 23 | let server: PostmanServer; 24 | 25 | beforeEach(() => { 26 | vi.clearAllMocks(); 27 | server = new PostmanServer(); 28 | }); 29 | 30 | it("should initialize server with proper configuration", async (): Promise => { 31 | const mcpServer = await server.start(); 32 | 33 | // Verify server is properly initialized with expected methods 34 | expect(mcpServer).toBeDefined(); 35 | expect(typeof mcpServer.connect).toBe("function"); 36 | expect(typeof mcpServer.close).toBe("function"); 37 | }); 38 | 39 | it("should properly instantiate NewmanRunner", () => { 40 | expect(MockedNewmanRunner).toHaveBeenCalledTimes(1); 41 | // Verify NewmanRunner was constructed with no arguments 42 | expect(MockedNewmanRunner).toHaveBeenCalledWith(); 43 | }); 44 | 45 | it("should register run-collection tool", async () => { 46 | const mcpServer = await server.start(); 47 | 48 | // Verify server is properly initialized 49 | expect(mcpServer).toBeDefined(); 50 | 51 | // Verify the server has the expected methods 52 | expect(typeof mcpServer.setRequestHandler).toBe("function"); 53 | expect(typeof mcpServer.connect).toBe("function"); 54 | }); 55 | 56 | it("should execute collection run with minimal parameters", async () => { 57 | // Setup mock response 58 | const mockResult = { 59 | success: true, 60 | summary: { total: 1, failed: 0, passed: 1 }, 61 | }; 62 | 63 | // Setup mock runner with spy 64 | const runCollectionSpy = vi.fn().mockResolvedValue(mockResult); 65 | const mockRunner = { runCollection: runCollectionSpy }; 66 | vi.mocked(NewmanRunner).mockImplementation(() => mockRunner); 67 | 68 | // Simulate running a collection 69 | await mockRunner.runCollection({ collection: "./test-collection.json" }); 70 | 71 | // Verify mock runner was called with correct parameters 72 | expect(runCollectionSpy).toHaveBeenCalledWith({ 73 | collection: "./test-collection.json", 74 | }); 75 | 76 | // Verify mock runner returned expected result 77 | expect(await runCollectionSpy.mock.results[0].value).toEqual(mockResult); 78 | }); 79 | 80 | it("should execute collection run with all parameters", async () => { 81 | // Setup mock response 82 | const mockResult = { 83 | success: true, 84 | summary: { 85 | total: 2, 86 | failed: 0, 87 | passed: 2, 88 | }, 89 | }; 90 | 91 | // Setup mock runner with spy 92 | const runCollectionSpy = vi.fn().mockResolvedValue(mockResult); 93 | const mockRunner = { runCollection: runCollectionSpy }; 94 | vi.mocked(NewmanRunner).mockImplementation(() => mockRunner); 95 | 96 | // Simulate running a collection with all parameters 97 | await mockRunner.runCollection({ 98 | collection: "./test-collection.json", 99 | environment: "./env.json", 100 | globals: "./globals.json", 101 | iterationCount: 2, 102 | }); 103 | 104 | // Verify mock runner was called with all parameters 105 | expect(runCollectionSpy).toHaveBeenCalledWith({ 106 | collection: "./test-collection.json", 107 | environment: "./env.json", 108 | globals: "./globals.json", 109 | iterationCount: 2, 110 | }); 111 | 112 | // Verify mock runner returned expected result 113 | expect(await runCollectionSpy.mock.results[0].value).toEqual(mockResult); 114 | }); 115 | 116 | it("should handle invalid collection path error", async () => { 117 | // Setup mock runner with spy that rejects 118 | const runCollectionSpy = vi 119 | .fn() 120 | .mockRejectedValue(new Error("Could not find collection file")); 121 | const mockRunner = { runCollection: runCollectionSpy }; 122 | vi.mocked(NewmanRunner).mockImplementation(() => mockRunner); 123 | 124 | // Simulate running with invalid collection path 125 | await expect( 126 | mockRunner.runCollection({ 127 | collection: "./invalid-collection.json", 128 | }), 129 | ).rejects.toThrow("Could not find collection file"); 130 | }); 131 | 132 | it("should handle invalid environment file error", async () => { 133 | // Setup mock runner with spy that rejects 134 | const runCollectionSpy = vi 135 | .fn() 136 | .mockRejectedValue(new Error("Could not find environment file")); 137 | const mockRunner = { runCollection: runCollectionSpy }; 138 | vi.mocked(NewmanRunner).mockImplementation(() => mockRunner); 139 | 140 | // Simulate running with invalid environment file 141 | await expect( 142 | mockRunner.runCollection({ 143 | collection: "./test-collection.json", 144 | environment: "./invalid-env.json", 145 | }), 146 | ).rejects.toThrow("Could not find environment file"); 147 | }); 148 | 149 | it("should handle invalid globals file error", async () => { 150 | // Setup mock runner with spy that rejects 151 | const runCollectionSpy = vi 152 | .fn() 153 | .mockRejectedValue(new Error("Could not find globals file")); 154 | const mockRunner = { runCollection: runCollectionSpy }; 155 | vi.mocked(NewmanRunner).mockImplementation(() => mockRunner); 156 | 157 | // Simulate running with invalid globals file 158 | await expect( 159 | mockRunner.runCollection({ 160 | collection: "./test-collection.json", 161 | globals: "./invalid-globals.json", 162 | }), 163 | ).rejects.toThrow("Could not find globals file"); 164 | }); 165 | 166 | it("should handle invalid iterationCount error", async () => { 167 | // Setup mock runner with spy that rejects 168 | const runCollectionSpy = vi 169 | .fn() 170 | .mockRejectedValue(new Error("iterationCount must be greater than 0")); 171 | const mockRunner = { runCollection: runCollectionSpy }; 172 | vi.mocked(NewmanRunner).mockImplementation(() => mockRunner); 173 | 174 | // Simulate running with invalid iteration count 175 | await expect( 176 | mockRunner.runCollection({ 177 | collection: "./test-collection.json", 178 | iterationCount: 0, 179 | }), 180 | ).rejects.toThrow("iterationCount must be greater than 0"); 181 | }); 182 | 183 | it("should reject when collection parameter is missing", async () => { 184 | // Setup mock runner with spy that rejects 185 | const runCollectionSpy = vi 186 | .fn() 187 | .mockRejectedValue(new Error("collection parameter is required")); 188 | const mockRunner = { runCollection: runCollectionSpy }; 189 | vi.mocked(NewmanRunner).mockImplementation(() => mockRunner); 190 | 191 | // Simulate running without collection parameter 192 | await expect( 193 | mockRunner.runCollection({} as Partial), 194 | ).rejects.toThrow("collection parameter is required"); 195 | }); 196 | 197 | it("should reject when iterationCount is less than 1", async () => { 198 | // Setup mock runner with spy that rejects 199 | const runCollectionSpy = vi 200 | .fn() 201 | .mockRejectedValue(new Error("iterationCount must be greater than 0")); 202 | const mockRunner = { runCollection: runCollectionSpy }; 203 | vi.mocked(NewmanRunner).mockImplementation(() => mockRunner); 204 | 205 | // Simulate running with invalid iteration count 206 | await expect( 207 | mockRunner.runCollection({ 208 | collection: "./test-collection.json", 209 | iterationCount: -1, 210 | }), 211 | ).rejects.toThrow("iterationCount must be greater than 0"); 212 | }); 213 | 214 | it("should reject when unknown tool name is provided", async () => { 215 | // Setup mock runner with spy that rejects 216 | const runCollectionSpy = vi 217 | .fn() 218 | .mockRejectedValue(new Error("unknown tool parameter")); 219 | const mockRunner = { runCollection: runCollectionSpy }; 220 | vi.mocked(NewmanRunner).mockImplementation(() => mockRunner); 221 | 222 | // Simulate running with unknown tool name 223 | await expect( 224 | mockRunner.runCollection({ 225 | collection: "./test-collection.json", 226 | tool: "unknown-tool", 227 | } as CollectionRunParams & { tool: string }), 228 | ).rejects.toThrow("unknown tool parameter"); 229 | }); 230 | 231 | it("should handle invalid input types", async () => { 232 | // Setup mock runner with spy that rejects 233 | const runCollectionSpy = vi 234 | .fn() 235 | .mockRejectedValue(new Error("collection must be a string")); 236 | const mockRunner = { runCollection: runCollectionSpy }; 237 | vi.mocked(NewmanRunner).mockImplementation(() => mockRunner); 238 | 239 | // Simulate running with invalid input type 240 | await expect( 241 | mockRunner.runCollection({ 242 | collection: 123, 243 | } as unknown as CollectionRunParams), 244 | ).rejects.toThrow("collection must be a string"); 245 | }); 246 | }); 247 | -------------------------------------------------------------------------------- /example/prompts/CODING_STANDARDS.md: -------------------------------------------------------------------------------- 1 | # Express API Coding Standards 2 | 3 | This document outlines the coding standards and best practices for building REST APIs with Express.js in this project. Following these standards ensures consistency, maintainability, and scalability of our APIs. 4 | 5 | ## Table of Contents 6 | 7 | 1. [Project Structure](#project-structure) 8 | 2. [API Design Principles](#api-design-principles) 9 | 3. [Error Handling](#error-handling) 10 | 4. [Security](#security) 11 | 5. [Performance](#performance) 12 | 6. [Documentation](#documentation) 13 | 7. [Testing](#testing) 14 | 15 | ## Project Structure 16 | 17 | ### Directory Layout 18 | 19 | ``` 20 | src/ 21 | ├── index.ts # Application entry point 22 | ├── routes/ # Route definitions 23 | │ ├── index.ts # Route aggregator 24 | │ └── entity/ # Entity-specific routes 25 | ├── controllers/ # Request handlers 26 | │ └── entity/ # Entity-specific controllers 27 | ├── services/ # Business logic 28 | │ └── entity/ # Entity-specific services 29 | ├── models/ # Data models 30 | │ └── entity/ # Entity-specific models 31 | ├── middleware/ # Custom middleware 32 | ├── utils/ # Utility functions 33 | └── types/ # TypeScript type definitions 34 | ``` 35 | 36 | ### Three-Layer Architecture 37 | 38 | 1. **Web Layer (Controllers & Routes)** 39 | 40 | - Handles HTTP requests/responses 41 | - Input validation 42 | - Route definitions 43 | - No business logic 44 | 45 | 2. **Service Layer** 46 | 47 | - Contains business logic 48 | - Orchestrates data access 49 | - Independent of HTTP context 50 | 51 | 3. **Data Access Layer** 52 | - Database interactions 53 | - Data models 54 | - Query operations 55 | 56 | ## API Design Principles 57 | 58 | ### URL Structure 59 | 60 | - Use plural nouns for resources 61 | - Use kebab-case for URLs 62 | - Nest related resources 63 | 64 | ```typescript 65 | // Good 66 | GET /api/v1/users 67 | GET /api/v1/users/:userId/orders 68 | 69 | // Bad 70 | GET /api/v1/getUser 71 | GET /api/v1/user-get 72 | ``` 73 | 74 | ### HTTP Methods 75 | 76 | Use appropriate HTTP methods: 77 | 78 | - GET: Retrieve resources 79 | - POST: Create resources 80 | - PUT: Update entire resources 81 | - PATCH: Partial updates 82 | - DELETE: Remove resources 83 | 84 | ### Request/Response Format 85 | 86 | ```typescript 87 | // Success Response 88 | { 89 | "status": "success", 90 | "data": { 91 | // Response data 92 | } 93 | } 94 | 95 | // Error Response 96 | { 97 | "status": "error", 98 | "error": { 99 | "code": "ERROR_CODE", 100 | "message": "Human readable message" 101 | } 102 | } 103 | ``` 104 | 105 | ### Versioning 106 | 107 | - Include version in URL path 108 | - Start with v1 109 | 110 | ```typescript 111 | app.use("/api/v1/users", userRoutes); 112 | ``` 113 | 114 | ## Error Handling 115 | 116 | ### HTTP Status Codes 117 | 118 | Use appropriate status codes: 119 | 120 | - 200: Success 121 | - 201: Created 122 | - 400: Bad Request 123 | - 401: Unauthorized 124 | - 403: Forbidden 125 | - 404: Not Found 126 | - 500: Internal Server Error 127 | 128 | ### Error Handler Implementation 129 | 130 | ```typescript 131 | // Global error handler 132 | app.use((err: Error, req: Request, res: Response, next: NextFunction) => { 133 | const status = err.status || 500; 134 | const message = err.message || "Internal server error"; 135 | 136 | res.status(status).json({ 137 | status: "error", 138 | error: { 139 | message, 140 | ...(process.env.NODE_ENV === "development" && { stack: err.stack }), 141 | }, 142 | }); 143 | }); 144 | ``` 145 | 146 | ### Custom Error Classes 147 | 148 | ```typescript 149 | class APIError extends Error { 150 | constructor( 151 | public status: number, 152 | public message: string, 153 | public code?: string 154 | ) { 155 | super(message); 156 | this.name = "APIError"; 157 | } 158 | } 159 | ``` 160 | 161 | ## Security 162 | 163 | ### Authentication & Authorization 164 | 165 | - Use JWT for stateless authentication 166 | - Implement role-based access control 167 | - Store sensitive data in environment variables 168 | 169 | ### Middleware Security 170 | 171 | ```typescript 172 | import helmet from "helmet"; 173 | import rateLimit from "express-rate-limit"; 174 | 175 | // Security headers 176 | app.use(helmet()); 177 | 178 | // Rate limiting 179 | const limiter = rateLimit({ 180 | windowMs: 15 * 60 * 1000, // 15 minutes 181 | max: 100, // limit each IP to 100 requests per windowMs 182 | }); 183 | app.use(limiter); 184 | ``` 185 | 186 | ### Input Validation 187 | 188 | Use a validation library (e.g., express-validator): 189 | 190 | ```typescript 191 | import { body, validationResult } from "express-validator"; 192 | 193 | const validateUser = [ 194 | body("email").isEmail(), 195 | body("password").isLength({ min: 6 }), 196 | (req: Request, res: Response, next: NextFunction) => { 197 | const errors = validationResult(req); 198 | if (!errors.isEmpty()) { 199 | return res.status(400).json({ errors: errors.array() }); 200 | } 201 | next(); 202 | }, 203 | ]; 204 | ``` 205 | 206 | ## Performance 207 | 208 | ### Caching 209 | 210 | Implement appropriate caching strategies: 211 | 212 | ```typescript 213 | import apicache from "apicache"; 214 | 215 | // Cache successful GET requests for 15 minutes 216 | app.use(apicache.middleware("15 minutes")); 217 | ``` 218 | 219 | ### Database Optimization 220 | 221 | - Use indexes appropriately 222 | - Implement pagination 223 | - Optimize queries 224 | 225 | ### Request Handling 226 | 227 | ```typescript 228 | // Pagination example 229 | router.get("/users", async (req: Request, res: Response) => { 230 | const page = parseInt(req.query.page as string) || 1; 231 | const limit = parseInt(req.query.limit as string) || 10; 232 | const skip = (page - 1) * limit; 233 | 234 | const users = await User.find().skip(skip).limit(limit); 235 | 236 | res.json({ 237 | status: "success", 238 | data: users, 239 | pagination: { 240 | page, 241 | limit, 242 | total: await User.countDocuments(), 243 | }, 244 | }); 245 | }); 246 | ``` 247 | 248 | ## Documentation 249 | 250 | ### API Documentation 251 | 252 | Use OpenAPI/Swagger for API documentation: 253 | 254 | ```typescript 255 | /** 256 | * @swagger 257 | * /api/v1/users: 258 | * get: 259 | * summary: Retrieve users 260 | * parameters: 261 | * - in: query 262 | * name: page 263 | * schema: 264 | * type: integer 265 | * description: Page number 266 | * responses: 267 | * 200: 268 | * description: List of users 269 | */ 270 | ``` 271 | 272 | ### Code Documentation 273 | 274 | - Use JSDoc for function documentation 275 | - Document complex business logic 276 | - Include examples for non-obvious implementations 277 | 278 | ## Testing 279 | 280 | ### Unit Tests 281 | 282 | ```typescript 283 | describe("UserService", () => { 284 | it("should create a new user", async () => { 285 | const userData = { 286 | email: "test@example.com", 287 | password: "password123", 288 | }; 289 | const user = await UserService.create(userData); 290 | expect(user).toHaveProperty("id"); 291 | expect(user.email).toBe(userData.email); 292 | }); 293 | }); 294 | ``` 295 | 296 | ### Integration Tests 297 | 298 | ```typescript 299 | describe("User API", () => { 300 | it("should return 401 for unauthorized access", async () => { 301 | const response = await request(app) 302 | .get("/api/v1/users") 303 | .set("Accept", "application/json"); 304 | 305 | expect(response.status).toBe(401); 306 | }); 307 | }); 308 | ``` 309 | 310 | ### API Testing 311 | 312 | - Use Postman/Newman for API testing 313 | - Maintain collection of API tests 314 | - Include environment-specific configurations 315 | 316 | ## Additional Guidelines 317 | 318 | 1. **Dependency Management** 319 | 320 | - Keep dependencies up to date 321 | - Use exact versions in package.json 322 | - Regularly audit dependencies for security 323 | 324 | 2. **Code Quality** 325 | 326 | - Use ESLint for code linting 327 | - Implement pre-commit hooks 328 | - Follow TypeScript best practices 329 | 330 | 3. **Logging** 331 | 332 | - Implement structured logging 333 | - Use appropriate log levels 334 | - Include request correlation IDs 335 | 336 | 4. **Configuration** 337 | 338 | - Use environment variables for configuration 339 | - Implement configuration validation 340 | - Maintain separate configs for different environments 341 | 342 | 5. **Monitoring** 343 | - Implement health checks 344 | - Monitor API metrics 345 | - Set up error tracking 346 | 347 | ## ERROR Handling Examples 348 | 349 | When displaying error or status the following is a working example of an error case 350 | 351 | res.status(500).json({ error: "Something went wrong!" }); 352 | 353 | ``` 354 | app.use((err: Error, _req: Request, res: Response) => { 355 | console.error(err.stack); 356 | res.status(500).json({ error: "Something went wrong!" }); 357 | }); 358 | ``` 359 | 360 | Here is another example of returning a 400 status with error code 361 | 362 | ``` 363 | 364 | // Validate request body 365 | if (!name || !description) { 366 | return res.status(400).json({ error: "Name and description are required" }); 367 | } 368 | ``` 369 | 370 | Here is success example 371 | 372 | ``` 373 | res.status(201).json(newItem); 374 | ``` 375 | 376 | IMPORTANT. ALWAYS USE 377 | res.status(XXX).json() when responding 378 | 379 | Never use 380 | 381 | ``` 382 | res.json({ 383 | status: "success", 384 | data: response.data 385 | }); 386 | ``` 387 | 388 | As you are not setting a status 389 | 390 | Remember: These standards are guidelines, not rigid rules. Use judgment to determine when to deviate based on specific requirements or constraints. 391 | -------------------------------------------------------------------------------- /prompts/POSTMAN_NEWMAN_REFERENCE_DOCS.md: -------------------------------------------------------------------------------- 1 | The following is reference documentation on Postman and Newman 2 | 3 | ### 4 | 5 | Newman is a command-line collection runner for Postman. It allows you to effortlessly run and test a Postman collection directly from the command-line. It is built with extensibility in mind so that you can easily integrate it with your continuous integration servers and build systems. 6 | 7 | Table of contents 8 | Getting Started 9 | Usage 10 | Using Newman CLI 11 | Using Newman as a Library 12 | Using Reporters with Newman 13 | Command Line Options 14 | newman-options 15 | newman-run 16 | SSL 17 | Configuring Proxy 18 | API Reference 19 | newman run 20 | Run summary object 21 | Events emitted during a collection run 22 | Reporters 23 | Configuring Reporters 24 | CLI Reporter 25 | JSON Reporter 26 | JUnit Reporter 27 | HTML Reporter 28 | External Reporters 29 | Using External Reporters 30 | Creating Your Own Reporter 31 | File Uploads 32 | Using Newman with the Postman API 33 | Using Newman in Docker 34 | Using Socks Proxy 35 | Migration Guide 36 | Compatibility 37 | Contributing 38 | Community Support 39 | License 40 | Getting started 41 | To run Newman, ensure that you have Node.js >= v16. Install Node.js via package manager. 42 | 43 | Installation 44 | The easiest way to install Newman is using NPM. If you have Node.js installed, it is most likely that you have NPM installed as well. 45 | 46 | $ npm install -g newman 47 | This installs Newman globally on your system allowing you to run it from anywhere. If you want to install it locally, Just remove the -g flag. 48 | 49 | Using Homebrew 50 | Install Newman globally on your system using Homebrew. 51 | 52 | $ brew install newman 53 | back to top 54 | 55 | Usage 56 | Using Newman CLI 57 | The newman run command allows you to specify a collection to be run. You can easily export your Postman Collection as a json file from the Postman App and run it using Newman. 58 | 59 | $ newman run examples/sample-collection.json 60 | If your collection file is available as an URL (such as from our Cloud API service), Newman can fetch your file and run it as well. 61 | 62 | $ newman run https://www.getpostman.com/collections/631643-f695cab7-6878-eb55-7943-ad88e1ccfd65-JsLv 63 | For the complete list of options, refer the Command Line Options section below. 64 | 65 | terminal-demo 66 | 67 | Using Newman as a Library 68 | Newman can be easily used within your JavaScript projects as a Node.js module. The entire set of Newman CLI functionality is available for programmatic use as well. The following example runs a collection by reading a JSON collection file stored on disk. 69 | 70 | const newman = require('newman'); // require newman in your project 71 | 72 | // call newman.run to pass `options` object and wait for callback 73 | newman.run({ 74 | collection: require('./sample-collection.json'), 75 | reporters: 'cli' 76 | }, function (err) { 77 | if (err) { throw err; } 78 | console.log('collection run complete!'); 79 | }); 80 | For the complete list of options, refer the API Reference section below. 81 | 82 | Using Reporters with Newman 83 | Reporters provide information about the current collection run in a format that is easy to both: disseminate and assimilate. Reporters can be configured using the -r or --reporters options. Inbuilt reporters in newman are: cli, json, junit, progress and emojitrain. 84 | 85 | CLI reporter is enabled by default when Newman is used as a CLI, you do not need to specifically provide the same as part of reporters option. However, enabling one or more of the other reporters will result in no CLI output. Explicitly enable the CLI option in such a scenario. Check the example given below using the CLI and JSON reporters: 86 | 87 | $ newman run examples/sample-collection.json -r cli,json 88 | For more details on Reporters and writing your own External Reporters refer to their corresponding sections below. 89 | 90 | back to top 91 | 92 | Command Line Options 93 | newman [options] 94 | -h, --help 95 | Show command line help, including a list of options, and sample use cases. 96 | 97 | -v, --version 98 | Displays the current Newman version, taken from package.json 99 | 100 | newman run [options] 101 | -e , --environment 102 | Specify an environment file path or URL. Environments provide a set of variables that one can use within collections. Read More 103 | 104 | -g , --globals 105 | Specify the file path or URL for global variables. Global variables are similar to environment variables but have a lower precedence and can be overridden by environment variables having the same name. 106 | 107 | -d , --iteration-data 108 | Specify a data source file (JSON or CSV) to be used for iteration as a path to a file or as a URL. Read More 109 | 110 | -n , --iteration-count 111 | Specifies the number of times the collection has to be run when used in conjunction with iteration data file. 112 | 113 | --folder 114 | Run requests within a particular folder/folders or specific requests in a collection. Multiple folders or requests can be specified by using --folder multiple times, like so: --folder f1 --folder f2 --folder r1 --folder r2. 115 | 116 | --working-dir 117 | Set the path of the working directory to use while reading files with relative paths. Default to current directory. 118 | 119 | --no-insecure-file-read 120 | Prevents reading of files situated outside of the working directory. 121 | 122 | --export-environment 123 | The path to the file where Newman will output the final environment variables file before completing a run. 124 | 125 | --export-globals 126 | The path to the file where Newman will output the final global variables file before completing a run. 127 | 128 | --export-collection 129 | The path to the file where Newman will output the final collection file before completing a run. 130 | 131 | --timeout 132 | Specify the time (in milliseconds) to wait for the entire collection run to complete execution. 133 | 134 | --timeout-request 135 | Specify the time (in milliseconds) to wait for requests to return a response. 136 | 137 | --timeout-script 138 | Specify the time (in milliseconds) to wait for scripts to complete execution. 139 | 140 | -k, --insecure 141 | Disables SSL verification checks and allows self-signed SSL certificates. 142 | 143 | --ignore-redirects 144 | Prevents newman from automatically following 3XX redirect responses. 145 | 146 | --delay-request 147 | Specify the extent of delay between requests (milliseconds). 148 | 149 | --cookie-jar 150 | Specify the file path for a JSON Cookie Jar. Uses tough-cookie to deserialize the file. 151 | 152 | --export-cookie-jar 153 | The path to the file where Newman will output the final cookie jar file before completing a run. Uses tough-cookie's serialize method. 154 | 155 | --bail [optional modifiers] 156 | Specify whether or not to stop a collection run on encountering the first test script error. 157 | Can optionally accept modifiers, currently include folder and failure. 158 | folder allows you to skip the entire collection run in case an invalid folder was specified using the --folder option or an error was encountered in general. 159 | On the failure of a test, failure would gracefully stop a collection run after completing the current test script. 160 | 161 | -x, --suppress-exit-code 162 | Specify whether or not to override the default exit code for the current run. 163 | 164 | --color 165 | Enable or Disable colored CLI output. The color value can be any of the three: on, off or auto(default). 166 | With auto, Newman attempts to automatically turn color on or off based on the color support in the terminal. This behaviour can be modified by using the on or off value accordingly. 167 | 168 | --disable-unicode 169 | Specify whether or not to force the unicode disable option. When supplied, all symbols in the output will be replaced by their plain text equivalents. 170 | 171 | --global-var "=" 172 | Allows the specification of global variables via the command line, in a key=value format. Multiple CLI global variables can be added by using --global-var multiple times, like so: --global-var "foo=bar" --global-var "alpha=beta". 173 | 174 | --env-var "=" 175 | Allows the specification of environment variables via the command line, in a key=value format. Multiple CLI environment variables can be added by using --env-var multiple times, like so: --env-var "foo=bar" --env-var "alpha=beta". 176 | 177 | --verbose 178 | Show detailed information of collection run and each request sent. 179 | 180 | SSL 181 | Client Certificates 182 | Client certificates are an alternative to traditional authentication mechanisms. These allow their users to make authenticated requests to a server, using a public certificate, and an optional private key that verifies certificate ownership. In some cases, the private key may also be protected by a secret passphrase, providing an additional layer of authentication security. 183 | 184 | Newman supports SSL client certificates, via the following CLI options: 185 | 186 | Using a single SSL client certificate 187 | --ssl-client-cert 188 | The path to the public client certificate file. 189 | 190 | --ssl-client-key 191 | The path to the private client key (optional). 192 | 193 | --ssl-client-passphrase 194 | The secret passphrase used to protect the private client key (optional). 195 | 196 | Using SSL client certificates configuration file (supports multiple certificates per run) 197 | --ssl-client-cert-list 198 | The path to the SSL client certificate list configuration file (JSON format). See examples/ssl-client-cert-list.json. 199 | This option allows setting different SSL client certificate according to URL or hostname. This option takes precedence over --ssl-client-cert, --ssl-client-key and --ssl-client-passphrase options. If there is no match for the URL in the list, these options are used as fallback. 200 | 201 | Trusted CA 202 | When it is not wanted to use the --insecure option, additionally trusted CA certificates can be provided like this: 203 | 204 | --ssl-extra-ca-certs 205 | The path to the file, that holds one or more trusted CA certificates in PEM format 206 | Configuring Proxy 207 | Newman can also be configured to work with proxy settings via the following environment variables: 208 | 209 | HTTP_PROXY / http_proxy 210 | HTTPS_PROXY / https_proxy 211 | NO_PROXY / no_proxy 212 | For more details on using these variables, refer here. 213 | 214 | back to top 215 | 216 | API Reference 217 | newman.run(options: object , callback: function) => run: EventEmitter 218 | The run function executes a collection and returns the run result to a callback function provided as parameter. The return of the newman.run function is a run instance, which emits run events that can be listened to. 219 | 220 | Parameter Description 221 | options This is a required argument and it contains all information pertaining to running a collection. 222 | 223 | Required 224 | Type: object 225 | options.collection The collection is a required property of the options argument. It accepts an object representation of a Postman Collection which should resemble the schema mentioned at https://schema.getpostman.com/. The value of this property could also be an instance of Collection Object from the Postman Collection SDK. 226 | 227 | As string, one can provide a URL where the Collection JSON can be found (e.g. Postman Cloud API service) or path to a local JSON file. 228 | 229 | Required 230 | Type: object|string PostmanCollection 231 | options.environment One can optionally pass an environment file path or URL as string to this property and that will be used to read Postman Environment Variables from. This property also accepts environment variables as an object. Environment files exported from Postman App can be directly used here. 232 | 233 | Optional 234 | Type: object|string 235 | options.envVar One can optionally pass environment variables as an array of key-value string object pairs. It will be used to read Postman Environment Variables as well as overwrite environment variables from options.environments. 236 | 237 | Optional 238 | Type: array|object 239 | options.globals Postman Global Variables can be optionally passed on to a collection run in form of path to a file or URL. It also accepts variables as an object. 240 | 241 | Optional 242 | Type: object|string 243 | options.globalVar One can optionally pass global environment variables as an array of key-value string object pairs. It will be used to read Postman Global Environment Variables as well as overwrite global environment variables from options.globals. 244 | 245 | Optional 246 | Type: array|object 247 | options.iterationCount Specify the number of iterations to run on the collection. This is usually accompanied by providing a data file reference as options.iterationData. 248 | 249 | Optional 250 | Type: number, Default value: 1 251 | options.iterationData Path to the JSON or CSV file or URL to be used as data source when running multiple iterations on a collection. 252 | 253 | Optional 254 | Type: string 255 | options.folder The name or ID of the folder/folders (ItemGroup) in the collection which would be run instead of the entire collection. 256 | 257 | Optional 258 | Type: string|array 259 | options.workingDir The path of the directory to be used as working directory. 260 | 261 | Optional 262 | Type: string, Default value: Current Directory 263 | options.insecureFileRead Allow reading files outside of working directory. 264 | 265 | Optional 266 | Type: boolean, Default value: true 267 | options.timeout Specify the time (in milliseconds) to wait for the entire collection run to complete execution. 268 | 269 | Optional 270 | Type: number, Default value: Infinity 271 | options.timeoutRequest Specify the time (in milliseconds) to wait for requests to return a response. 272 | 273 | Optional 274 | Type: number, Default value: Infinity 275 | options.timeoutScript Specify the time (in milliseconds) to wait for scripts to return a response. 276 | 277 | Optional 278 | Type: number, Default value: Infinity 279 | options.delayRequest Specify the time (in milliseconds) to wait for between subsequent requests. 280 | 281 | Optional 282 | Type: number, Default value: 0 283 | options.ignoreRedirects This specifies whether newman would automatically follow 3xx responses from servers. 284 | 285 | Optional 286 | Type: boolean, Default value: false 287 | options.insecure Disables SSL verification checks and allows self-signed SSL certificates. 288 | 289 | Optional 290 | Type: boolean, Default value: false 291 | options.bail A switch to specify whether or not to gracefully stop a collection run (after completing the current test script) on encountering the first error. Takes additional modifiers as arguments to specify whether to end the run with an error for invalid name or path. 292 | 293 | Available modifiers: folder and failure. 294 | eg. bail : ['folder'] 295 | 296 | Optional 297 | Type: boolean|object, Default value: false 298 | options.suppressExitCode If present, allows overriding the default exit code from the current collection run, useful for bypassing collection result failures. Takes no arguments. 299 | 300 | Optional 301 | Type: boolean, Default value: false 302 | options.reporters Specify one reporter name as string or provide more than one reporter name as an array. 303 | 304 | Available reporters: cli, json, junit, progress and emojitrain. 305 | 306 | Optional 307 | Type: string|array 308 | options.reporter Specify options for the reporter(s) declared in options.reporters. 309 | e.g. reporter : { junit : { export : './xmlResults.xml' } } 310 | e.g. reporter : { html : { export : './htmlResults.html', template: './customTemplate.hbs' } } 311 | 312 | Optional 313 | Type: object 314 | options.color Enable or Disable colored CLI output. 315 | 316 | Available options: on, off and auto 317 | 318 | Optional 319 | Type: string, Default value: auto 320 | options.sslClientCert The path to the public client certificate file. 321 | 322 | Optional 323 | Type: string 324 | options.sslClientKey The path to the private client key file. 325 | 326 | Optional 327 | Type: string 328 | options.sslClientPassphrase The secret client key passphrase. 329 | 330 | Optional 331 | Type: string 332 | options.sslClientCertList The path to the client certificate configuration list file. This option takes precedence over sslClientCert, sslClientKey and sslClientPassphrase. When there is no match in this configuration list, sslClientCert is used as fallback. 333 | 334 | Optional 335 | Type: string|array 336 | options.sslExtraCaCerts The path to the file, that holds one or more trusted CA certificates in PEM format. 337 | 338 | Optional 339 | Type: string 340 | options.requestAgents Specify the custom requesting agents to be used when performing HTTP and HTTPS requests respectively. Example: Using Socks Proxy 341 | 342 | Optional 343 | Type: object 344 | options.cookieJar One can optionally pass a CookieJar file path as string to this property and that will be deserialized using tough-cookie. This property also accepts a tough-cookie CookieJar instance. 345 | 346 | Optional 347 | Type: object|string 348 | options.newmanVersion The Newman version used for the collection run. 349 | 350 | This will be set by Newman 351 | callback Upon completion of the run, this callback is executed with the error, summary argument. 352 | 353 | Required 354 | Type: function 355 | newman.run~callback(error: object , summary: object) 356 | The callback parameter of the newman.run function receives two arguments: (1) error and (2) summary 357 | 358 | Argument Description 359 | error In case newman faces an error during the run, the error is passed on to this argument of callback. By default, only fatal errors, such as the ones caused by any fault inside Newman is passed on to this argument. However, setting abortOnError:true or abortOnFailure:true as part of run options will cause newman to treat collection script syntax errors and test failures as fatal errors and be passed down here while stopping the run abruptly at that point. 360 | 361 | Type: object 362 | summary The run summary will contain information pertaining to the run. 363 | 364 | Type: object 365 | summary.error An error object which if exists, contains an error message describing the message 366 | 367 | Type: object 368 | summary.collection This object contains information about the collection being run, it's requests, and their associated pre-request scripts and tests. 369 | 370 | Type: object 371 | summary.environment An object with environment variables used for the current run, and the usage status for each of those variables. 372 | 373 | Type: object 374 | summary.globals This object holds details about the globals used within the collection run namespace. 375 | 376 | Type: object 377 | summary.run A cumulative run summary object that provides information on . 378 | 379 | Type: object 380 | summary.run.stats An object which provides details about the total, failed, and pending counts for pre request scripts, tests, assertions, requests, and more. 381 | 382 | Type: object 383 | summary.run.failures An array of failure objects, with each element holding details, including the assertion that failed, and the request. 384 | 385 | Type: array. 386 | summary.run.executions This object contains information about each request, along with it's associated activities within the scope of the current collection run. 387 | 388 | Type: array. 389 | newman.run~events 390 | Newman triggers a whole bunch of events during the run. 391 | 392 | newman.run({ 393 | collection: require('./sample-collection.json'), 394 | iterationData: [{ "var": "data", "var_beta": "other_val" }], 395 | globals: { 396 | "id": "5bfde907-2a1e-8c5a-2246-4aff74b74236", 397 | "name": "test-env", 398 | "values": [ 399 | { 400 | "key": "alpha", 401 | "value": "beta", 402 | "type": "text", 403 | "enabled": true 404 | } 405 | ], 406 | "timestamp": 1404119927461, 407 | "\_postman_variable_scope": "globals", 408 | "\_postman_exported_at": "2016-10-17T14:31:26.200Z", 409 | "\_postman_exported_using": "Postman/4.8.0" 410 | }, 411 | globalVar: [ 412 | { "key":"glboalSecret", "value":"globalSecretValue" }, 413 | { "key":"globalAnotherSecret", "value":`${process.env.GLOBAL_ANOTHER_SECRET}`} 414 | ], 415 | environment: { 416 | "id": "4454509f-00c3-fd32-d56c-ac1537f31415", 417 | "name": "test-env", 418 | "values": [ 419 | { 420 | "key": "foo", 421 | "value": "bar", 422 | "type": "text", 423 | "enabled": true 424 | } 425 | ], 426 | "timestamp": 1404119927461, 427 | "\_postman_variable_scope": "environment", 428 | "\_postman_exported_at": "2016-10-17T14:26:34.940Z", 429 | "\_postman_exported_using": "Postman/4.8.0" 430 | }, 431 | envVar: [ 432 | { "key":"secret", "value":"secretValue" }, 433 | { "key":"anotherSecret", "value":`${process.env.ANOTHER_SECRET}`} 434 | ], 435 | }).on('start', function (err, args) { // on start of run, log to console 436 | console.log('running a collection...'); 437 | }).on('done', function (err, summary) { 438 | if (err || summary.error) { 439 | console.error('collection run encountered an error.'); 440 | } 441 | else { 442 | console.log('collection run completed.'); 443 | } 444 | }); 445 | All events receive two arguments (1) error and (2) args. The list below describes the properties of the second argument object. Learn more 446 | 447 | Event Description 448 | start The start of a collection run 449 | beforeIteration Before an iteration commences 450 | beforeItem Before an item execution begins (the set of prerequest->request->test) 451 | beforePrerequest Before prerequest script is execution starts 452 | prerequest After prerequest script execution completes 453 | beforeRequest Before an HTTP request is sent 454 | request After response of the request is received 455 | beforeTest Before test script is execution starts 456 | test After test script execution completes 457 | beforeScript Before any script (of type test or prerequest) is executed 458 | script After any script (of type test or prerequest) is executed 459 | item When an item (the whole set of prerequest->request->test) completes 460 | iteration After an iteration completes 461 | assertion This event is triggered for every test assertion done within test scripts 462 | console Every time a console function is called from within any script, this event is propagated 463 | exception When any asynchronous error happen in scripts this event is triggered 464 | beforeDone An event that is triggered prior to the completion of the run 465 | done This event is emitted when a collection run has completed, with or without errors 466 | back to top 467 | 468 | Reporters 469 | Configuring Reporters 470 | -r , --reporters 471 | Specify one reporter name as string or provide more than one reporter name as a comma separated list of reporter names. Available reporters are: cli, json, junit, progress and emojitrain. 472 | 473 | Spaces should not be used between reporter names / commas whilst specifying a comma separated list of reporters. For instance: 474 | 475 | ✅ -r cli,json,junit 476 | ❌ -r cli , json,junit 477 | 478 | --reporter-{{reporter-name}}-{{reporter-option}} 479 | When multiple reporters are provided, if one needs to specifically override or provide an option to one reporter, this is achieved by prefixing the option with --reporter-{{reporter-name}}-. 480 | 481 | For example, ... --reporters cli,json --reporter-cli-silent would silence the CLI reporter only. 482 | 483 | --reporter-{{reporter-options}} 484 | If more than one reporter accepts the same option name, they can be provided using the common reporter option syntax. 485 | 486 | For example, ... --reporters cli,json --reporter-silent passes the silent: true option to both JSON and CLI reporter. 487 | 488 | Note: Sample collection reports have been provided in examples/reports. 489 | 490 | CLI Reporter 491 | The built-in CLI reporter supports the following options, use them with appropriate argument switch prefix. For example, the option no-summary can be passed as --reporter-no-summary or --reporter-cli-no-summary. 492 | 493 | CLI reporter is enabled by default when Newman is used as a CLI, you do not need to specifically provide the same as part of --reporters option. However, enabling one or more of the other reporters will result in no CLI output. Explicitly enable the CLI option in such a scenario. 494 | 495 | CLI Option Description 496 | --reporter-cli-silent The CLI reporter is internally disabled and you see no output to terminal. 497 | | --reporter-cli-show-timestamps | This prints the local time for each request made. | | --reporter-cli-no-summary | The statistical summary table is not shown. | | --reporter-cli-no-failures | This prevents the run failures from being separately printed. | | --reporter-cli-no-assertions | This turns off the output for request-wise assertions as they happen. | | --reporter-cli-no-success-assertions | This turns off the output for successful assertions as they happen. | | --reporter-cli-no-console | This turns off the output of console.log (and other console calls) from collection's scripts. | | --reporter-cli-no-banner | This turns off the newman banner shown at the beginning of each collection run. | 498 | 499 | JSON Reporter 500 | The built-in JSON reporter is useful in producing a comprehensive output of the run summary. It takes the path to the file where to write the report. The content of this file is exactly the same as the summary parameter sent to the callback when Newman is used as a library. 501 | 502 | To enable JSON reporter, provide --reporters json as a CLI option. 503 | 504 | CLI Option Description 505 | --reporter-json-export Specify a path where the output JSON file will be written to disk. If not specified, the file will be written to newman/ in the current working directory. If the specified path does not exist, it will be created. However, if the specified path is a pre-existing directory, the report will be generated in that directory. 506 | JUNIT/XML Reporter 507 | The built-in JUnit reporter can output a summary of the collection run to a JUnit compatible XML file. To enable the JUNIT reporter, provide --reporters junit as a CLI option. 508 | 509 | CLI Option Description 510 | --reporter-junit-export Specify a path where the output XML file will be written to disk. If not specified, the file will be written to newman/ in the current working directory. If the specified path does not exist, it will be created. However, if the specified path is a pre-existing directory, the report will be generated in that directory. 511 | HTML Reporter 512 | An external reporter, maintained by Postman, which can be installed via npm install -g newman-reporter-html. This reporter was part of the Newman project but was separated out into its own project in V4. 513 | 514 | The complete installation and usage guide is available at newman-reporter-html. Once the HTML reporter is installed you can provide --reporters html as a CLI option. 515 | 516 | back to top 517 | 518 | External Reporters 519 | Using External Reporters 520 | Newman also supports external reporters, provided that the reporter works with Newman's event sequence. Working examples of how Newman reporters work can be found in lib/reporters. 521 | 522 | For instance, to use the Newman HTML Reporter: 523 | 524 | Install the reporter package. Note that the name of the package is of the form newman-reporter-. The installation should be global if Newman is installed globally, local otherwise. (Remove -g flag from the command below for a local installation.) 525 | $ npm install -g newman-reporter-html 526 | Use the installed reporter, either via the CLI, or programmatic usage. Here, the newman-reporter prefix is not required while specifying the reporter name in the options. 527 | $ newman run /path/to/collection.json -r cli,html 528 | const newman = require('newman'); 529 | 530 | newman.run({ 531 | collection: '/path/to/collection.json', 532 | reporters: ['cli', 'html'] 533 | }, process.exit); 534 | Community Maintained Reporters 535 | Several members of the Postman community have created custom reporters offering different option to output the data coming from Newman. Listed below is a selection of these but more can be found here on NPM. 536 | 537 | Once the custom reporter NPM package has been installed either globally or locally, this can be then used with Newman in the following ways: 538 | 539 | $ newman run /path/to/collection.json -r htmlextra,csv 540 | const newman = require('newman'); 541 | 542 | newman.run({ 543 | collection: '/path/to/collection.json', 544 | reporters: ['htmlextra', 'csv'] 545 | }, process.exit); 546 | allure - This reporter allow to create fully-featured allure reports that can allow you to have easy to understand HTML reports with features like historical data, link tests to the JIRA and all other benefits of using allure framework. 547 | htmlextra - This is an updated version of the standard HTML reporter containing a more in-depth data output and a few helpful extras 548 | csv - This reporter creates a csv file containing the high level summary of the Collection run 549 | json-summary - A Newman JSON Reporter that strips the results down to a minimum 550 | teamcity - A reporter built to be used with the Team City CI server 551 | testrail - A reporter built for Test Rail, the test case management tool 552 | statsd - This reporter can be used to send the Collection run data to statsd and used on time series analytic tools like Grafana 553 | confluence - Confluence reporter for Newman that uploads a Newman report on a Confluence page 554 | influxdb - This reporter sends the test results information to InfluxDB which can be used from Grafana to build dashboards 555 | Creating Your Own Reporter 556 | A custom reporter is a Node module with a name of the form newman-reporter-. To create a custom reporter: 557 | 558 | Navigate to a directory of your choice, and create a blank npm package with npm init. 559 | Add an index.js file, that exports a function of the following form: 560 | function CustomNewmanReporter (emitter, reporterOptions, collectionRunOptions) { 561 | // emitter is an event emitter that triggers the following events: https://github.com/postmanlabs/newman#newmanrunevents 562 | // reporterOptions is an object of the reporter specific options. See usage examples below for more details. 563 | // collectionRunOptions is an object of all the collection run options: https://github.com/postmanlabs/newman#newmanrunoptions-object--callback-function--run-eventemitter 564 | } 565 | module.exports = CustomNewmanReporter 566 | To use your reporter locally, use the npm pack command to create a .tgz file. Once created, this can be installed using the npm i -g newman-reporter-..tgz command. 567 | Once you're happy with your reporter, it can be published to npm using npm publish. This will then be made available for other people to download. 568 | 569 | Scoped reporter package names like @myorg/newman-reporter- are also supported. Working reporter examples can be found in lib/reporters. 570 | 571 | back to top 572 | 573 | File uploads 574 | Newman also supports file uploads for request form data. The files must be present in the current working directory. Your collection must also contain the filename in the "src" attribute of the request. 575 | 576 | In this collection, sample-file.txt should be present in the current working directory. 577 | 578 | { 579 | "info": { 580 | "name": "file-upload" 581 | }, 582 | "item": [ 583 | { 584 | "request": { 585 | "url": "https://postman-echo.com/post", 586 | "method": "POST", 587 | "body": { 588 | "mode": "formdata", 589 | "formdata": [ 590 | { 591 | "key": "file", 592 | "type": "file", 593 | "enabled": true, 594 | "src": "sample-file.txt" 595 | } 596 | ] 597 | } 598 | } 599 | } 600 | ] 601 | } 602 | $ ls 603 | file-upload.postman_collection.json sample-file.txt 604 | 605 | $ newman run file-upload.postman_collection.json 606 | back to top 607 | 608 | Using Newman with the Postman API 609 | 1 Generate an API key 610 | 2 Fetch a list of your collections from: https://api.getpostman.com/collections?apikey=$apiKey 611 | 3 Get the collection link via it's uid: https://api.getpostman.com/collections/$uid?apikey=$apiKey 612 | 4 Obtain the environment URI from: https://api.getpostman.com/environments?apikey=$apiKey 613 | 5 Using the collection and environment URIs acquired in steps 3 and 4, run the collection as follows: 614 | 615 | $ newman run "https://api.getpostman.com/collections/$uid?apikey=$apiKey" \ 616 | --environment "https://api.getpostman.com/environments/$uid?apikey=$apiKey" 617 | back to top 618 | 619 | Using Newman in Docker 620 | To use Newman in Docker check our docker documentation. 621 | 622 | Using Socks Proxy 623 | When using Newman as a library, you can pass a custom HTTP(S) agent which will be used for making the requests. Here's an example of how to setup socks proxy using a custom agent. 624 | 625 | const newman = require('newman'); 626 | const SocksProxyAgent = require('socks-proxy-agent'); 627 | const requestAgent = new SocksProxyAgent({ host: 'localhost', port: '1080' }); 628 | 629 | newman.run({ 630 | collection: require('./sample-collection.json'), 631 | requestAgents: { 632 | http: requestAgent, // agent used for HTTP requests 633 | https: requestAgent, // agent used for HTTPS requests 634 | } 635 | }, function (err) { 636 | if (err) { throw err; } 637 | console.log('collection run complete!'); 638 | }); 639 | back to top 640 | 641 | Migration Guide 642 | Newman v5 to v6 Migration Guide 643 | Newman v5.x Documentation 644 | Compatibility 645 | NodeJS 646 | Newman Node 647 | v3.x >= v4.x 648 | v4.x >= v6.x 649 | v5.x >= v10.x 650 | v6.x >= v16.x 651 | The current Node version compatibility can also be seen from the engines.node property in package.json 652 | 653 | File Encoding 654 | Newman attempts to detect file encoding for files that are provided as command line input. However, it mostly relies on NodeJS and the underlying operating system to do the heavy lifting. Currently, ASCII, UTF-8, UTF-16LE and ISO-8859-1 are the only ones that are detection assisted. 655 | 656 | ### 657 | -------------------------------------------------------------------------------- /prompts/MCP_REFERENCE_DOCS.md: -------------------------------------------------------------------------------- 1 | The following is information on building MCP Server Applications 2 | The following is from the documents from the MCP Site 3 | 4 | ### 5 | 6 | Get Started 7 | Introduction 8 | Get started with the Model Context Protocol (MCP) 9 | 10 | MCP is an open protocol that standardizes how applications provide context to LLMs. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools. 11 | 12 | ​ 13 | Why MCP? 14 | MCP helps you build agents and complex workflows on top of LLMs. LLMs frequently need to integrate with data and tools, and MCP provides: 15 | 16 | A growing list of pre-built integrations that your LLM can directly plug into 17 | The flexibility to switch between LLM providers and vendors 18 | Best practices for securing your data within your infrastructure 19 | ​ 20 | General architecture 21 | At its core, MCP follows a client-server architecture where a host application can connect to multiple servers: 22 | 23 | Internet 24 | 25 | Your Computer 26 | 27 | MCP Protocol 28 | 29 | MCP Protocol 30 | 31 | MCP Protocol 32 | 33 | Web APIs 34 | 35 | Host with MCP Client 36 | (Claude, IDEs, Tools) 37 | 38 | MCP Server A 39 | 40 | MCP Server B 41 | 42 | MCP Server C 43 | 44 | Local 45 | Data Source A 46 | 47 | Local 48 | Data Source B 49 | 50 | Remote 51 | Service C 52 | 53 | MCP Hosts: Programs like Claude Desktop, IDEs, or AI tools that want to access data through MCP 54 | MCP Clients: Protocol clients that maintain 1:1 connections with servers 55 | MCP Servers: Lightweight programs that each expose specific capabilities through the standardized Model Context Protocol 56 | Local Data Sources: Your computer’s files, databases, and services that MCP servers can securely access 57 | Remote Services: External systems available over the internet (e.g., through APIs) that MCP servers can connect to 58 | ​ 59 | 60 | ### 61 | 62 | Here is information on MCP Core Architecture 63 | 64 | ### 65 | 66 | Core architecture 67 | Understand how MCP connects clients, servers, and LLMs 68 | 69 | The Model Context Protocol (MCP) is built on a flexible, extensible architecture that enables seamless communication between LLM applications and integrations. This document covers the core architectural components and concepts. 70 | 71 | ​ 72 | Overview 73 | MCP follows a client-server architecture where: 74 | 75 | Hosts are LLM applications (like Claude Desktop or IDEs) that initiate connections 76 | Clients maintain 1:1 connections with servers, inside the host application 77 | Servers provide context, tools, and prompts to clients 78 | Server Process 79 | 80 | Server Process 81 | 82 | Host (e.g., Claude Desktop) 83 | 84 | Transport Layer 85 | 86 | Transport Layer 87 | 88 | MCP Client 89 | 90 | MCP Client 91 | 92 | MCP Server 93 | 94 | MCP Server 95 | 96 | ​ 97 | Core components 98 | ​ 99 | Protocol layer 100 | The protocol layer handles message framing, request/response linking, and high-level communication patterns. 101 | 102 | TypeScript 103 | Python 104 | 105 | class Protocol { 106 | // Handle incoming requests 107 | setRequestHandler(schema: T, handler: (request: T, extra: RequestHandlerExtra) => Promise): void 108 | 109 | // Handle incoming notifications 110 | setNotificationHandler(schema: T, handler: (notification: T) => Promise): void 111 | 112 | // Send requests and await responses 113 | request(request: Request, schema: T, options?: RequestOptions): Promise 114 | 115 | // Send one-way notifications 116 | notification(notification: Notification): Promise 117 | 118 | } 119 | Key classes include: 120 | 121 | Protocol 122 | Client 123 | Server 124 | ​ 125 | Transport layer 126 | The transport layer handles the actual communication between clients and servers. MCP supports multiple transport mechanisms: 127 | 128 | Stdio transport 129 | 130 | Uses standard input/output for communication 131 | Ideal for local processes 132 | HTTP with SSE transport 133 | 134 | Uses Server-Sent Events for server-to-client messages 135 | HTTP POST for client-to-server messages 136 | All transports use JSON-RPC 2.0 to exchange messages. See the specification for detailed information about the Model Context Protocol message format. 137 | 138 | ​ 139 | Message types 140 | MCP has these main types of messages: 141 | 142 | Requests expect a response from the other side: 143 | 144 | interface Request { 145 | method: string; 146 | params?: { ... }; 147 | } 148 | Results are successful responses to requests: 149 | 150 | interface Result { 151 | [key: string]: unknown; 152 | } 153 | Errors indicate that a request failed: 154 | 155 | interface Error { 156 | code: number; 157 | message: string; 158 | data?: unknown; 159 | } 160 | Notifications are one-way messages that don’t expect a response: 161 | 162 | interface Notification { 163 | method: string; 164 | params?: { ... }; 165 | } 166 | ​ 167 | Connection lifecycle 168 | ​ 169 | 170 | 1. Initialization 171 | Server 172 | Client 173 | Server 174 | Client 175 | Connection ready for use 176 | initialize request 177 | initialize response 178 | initialized notification 179 | Client sends initialize request with protocol version and capabilities 180 | Server responds with its protocol version and capabilities 181 | Client sends initialized notification as acknowledgment 182 | Normal message exchange begins 183 | ​ 184 | 2. Message exchange 185 | After initialization, the following patterns are supported: 186 | 187 | Request-Response: Client or server sends requests, the other responds 188 | Notifications: Either party sends one-way messages 189 | ​ 3. Termination 190 | Either party can terminate the connection: 191 | 192 | Clean shutdown via close() 193 | Transport disconnection 194 | Error conditions 195 | ​ 196 | Error handling 197 | MCP defines these standard error codes: 198 | 199 | enum ErrorCode { 200 | // Standard JSON-RPC error codes 201 | ParseError = -32700, 202 | InvalidRequest = -32600, 203 | MethodNotFound = -32601, 204 | InvalidParams = -32602, 205 | InternalError = -32603 206 | } 207 | SDKs and applications can define their own error codes above -32000. 208 | 209 | Errors are propagated through: 210 | 211 | Error responses to requests 212 | Error events on transports 213 | Protocol-level error handlers 214 | ​ 215 | Implementation example 216 | Here’s a basic example of implementing an MCP server: 217 | 218 | TypeScript 219 | Python 220 | 221 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 222 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 223 | 224 | const server = new Server({ 225 | name: "example-server", 226 | version: "1.0.0" 227 | }, { 228 | capabilities: { 229 | resources: {} 230 | } 231 | }); 232 | 233 | // Handle requests 234 | server.setRequestHandler(ListResourcesRequestSchema, async () => { 235 | return { 236 | resources: [ 237 | { 238 | uri: "example://resource", 239 | name: "Example Resource" 240 | } 241 | ] 242 | }; 243 | }); 244 | 245 | // Connect transport 246 | const transport = new StdioServerTransport(); 247 | await server.connect(transport); 248 | ​ 249 | Best practices 250 | ​ 251 | Transport selection 252 | Local communication 253 | 254 | Use stdio transport for local processes 255 | Efficient for same-machine communication 256 | Simple process management 257 | Remote communication 258 | 259 | Use SSE for scenarios requiring HTTP compatibility 260 | Consider security implications including authentication and authorization 261 | ​ 262 | Message handling 263 | Request processing 264 | 265 | Validate inputs thoroughly 266 | Use type-safe schemas 267 | Handle errors gracefully 268 | Implement timeouts 269 | Progress reporting 270 | 271 | Use progress tokens for long operations 272 | Report progress incrementally 273 | Include total progress when known 274 | Error management 275 | 276 | Use appropriate error codes 277 | Include helpful error messages 278 | Clean up resources on errors 279 | ​ 280 | Security considerations 281 | Transport security 282 | 283 | Use TLS for remote connections 284 | Validate connection origins 285 | Implement authentication when needed 286 | Message validation 287 | 288 | Validate all incoming messages 289 | Sanitize inputs 290 | Check message size limits 291 | Verify JSON-RPC format 292 | Resource protection 293 | 294 | Implement access controls 295 | Validate resource paths 296 | Monitor resource usage 297 | Rate limit requests 298 | Error handling 299 | 300 | Don’t leak sensitive information 301 | Log security-relevant errors 302 | Implement proper cleanup 303 | Handle DoS scenarios 304 | ​ 305 | Debugging and monitoring 306 | Logging 307 | 308 | Log protocol events 309 | Track message flow 310 | Monitor performance 311 | Record errors 312 | Diagnostics 313 | 314 | Implement health checks 315 | Monitor connection state 316 | Track resource usage 317 | Profile performance 318 | Testing 319 | 320 | Test different transports 321 | Verify error handling 322 | Check edge cases 323 | Load test servers 324 | 325 | ### 326 | 327 | Here is documentation onf MCP Resources 328 | 329 | ### 330 | 331 | Resources 332 | Expose data and content from your servers to LLMs 333 | 334 | Resources are a core primitive in the Model Context Protocol (MCP) that allow servers to expose data and content that can be read by clients and used as context for LLM interactions. 335 | 336 | Resources are designed to be application-controlled, meaning that the client application can decide how and when they should be used. Different MCP clients may handle resources differently. For example: 337 | 338 | Claude Desktop currently requires users to explicitly select resources before they can be used 339 | Other clients might automatically select resources based on heuristics 340 | Some implementations may even allow the AI model itself to determine which resources to use 341 | Server authors should be prepared to handle any of these interaction patterns when implementing resource support. In order to expose data to models automatically, server authors should use a model-controlled primitive such as Tools. 342 | 343 | ​ 344 | Overview 345 | Resources represent any kind of data that an MCP server wants to make available to clients. This can include: 346 | 347 | File contents 348 | Database records 349 | API responses 350 | Live system data 351 | Screenshots and images 352 | Log files 353 | And more 354 | Each resource is identified by a unique URI and can contain either text or binary data. 355 | 356 | ​ 357 | Resource URIs 358 | Resources are identified using URIs that follow this format: 359 | 360 | [protocol]://[host]/[path] 361 | For example: 362 | 363 | file:///home/user/documents/report.pdf 364 | postgres://database/customers/schema 365 | screen://localhost/display1 366 | The protocol and path structure is defined by the MCP server implementation. Servers can define their own custom URI schemes. 367 | 368 | ​ 369 | Resource types 370 | Resources can contain two types of content: 371 | 372 | ​ 373 | Text resources 374 | Text resources contain UTF-8 encoded text data. These are suitable for: 375 | 376 | Source code 377 | Configuration files 378 | Log files 379 | JSON/XML data 380 | Plain text 381 | ​ 382 | Binary resources 383 | Binary resources contain raw binary data encoded in base64. These are suitable for: 384 | 385 | Images 386 | PDFs 387 | Audio files 388 | Video files 389 | Other non-text formats 390 | ​ 391 | Resource discovery 392 | Clients can discover available resources through two main methods: 393 | 394 | ​ 395 | Direct resources 396 | Servers expose a list of concrete resources via the resources/list endpoint. Each resource includes: 397 | 398 | { 399 | uri: string; // Unique identifier for the resource 400 | name: string; // Human-readable name 401 | description?: string; // Optional description 402 | mimeType?: string; // Optional MIME type 403 | } 404 | ​ 405 | Resource templates 406 | For dynamic resources, servers can expose URI templates that clients can use to construct valid resource URIs: 407 | 408 | { 409 | uriTemplate: string; // URI template following RFC 6570 410 | name: string; // Human-readable name for this type 411 | description?: string; // Optional description 412 | mimeType?: string; // Optional MIME type for all matching resources 413 | } 414 | ​ 415 | Reading resources 416 | To read a resource, clients make a resources/read request with the resource URI. 417 | 418 | The server responds with a list of resource contents: 419 | 420 | { 421 | contents: [ 422 | { 423 | uri: string; // The URI of the resource 424 | mimeType?: string; // Optional MIME type 425 | 426 | // One of: 427 | text?: string; // For text resources 428 | blob?: string; // For binary resources (base64 encoded) 429 | } 430 | 431 | ] 432 | } 433 | Servers may return multiple resources in response to one resources/read request. This could be used, for example, to return a list of files inside a directory when the directory is read. 434 | 435 | ​ 436 | Resource updates 437 | MCP supports real-time updates for resources through two mechanisms: 438 | 439 | ​ 440 | List changes 441 | Servers can notify clients when their list of available resources changes via the notifications/resources/list_changed notification. 442 | 443 | ​ 444 | Content changes 445 | Clients can subscribe to updates for specific resources: 446 | 447 | Client sends resources/subscribe with resource URI 448 | Server sends notifications/resources/updated when the resource changes 449 | Client can fetch latest content with resources/read 450 | Client can unsubscribe with resources/unsubscribe 451 | ​ 452 | Example implementation 453 | Here’s a simple example of implementing resource support in an MCP server: 454 | 455 | TypeScript 456 | Python 457 | 458 | const server = new Server({ 459 | name: "example-server", 460 | version: "1.0.0" 461 | }, { 462 | capabilities: { 463 | resources: {} 464 | } 465 | }); 466 | 467 | // List available resources 468 | server.setRequestHandler(ListResourcesRequestSchema, async () => { 469 | return { 470 | resources: [ 471 | { 472 | uri: "file:///logs/app.log", 473 | name: "Application Logs", 474 | mimeType: "text/plain" 475 | } 476 | ] 477 | }; 478 | }); 479 | 480 | // Read resource contents 481 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => { 482 | const uri = request.params.uri; 483 | 484 | if (uri === "file:///logs/app.log") { 485 | const logContents = await readLogFile(); 486 | return { 487 | contents: [ 488 | { 489 | uri, 490 | mimeType: "text/plain", 491 | text: logContents 492 | } 493 | ] 494 | }; 495 | } 496 | 497 | throw new Error("Resource not found"); 498 | }); 499 | ​ 500 | Best practices 501 | When implementing resource support: 502 | 503 | Use clear, descriptive resource names and URIs 504 | Include helpful descriptions to guide LLM understanding 505 | Set appropriate MIME types when known 506 | Implement resource templates for dynamic content 507 | Use subscriptions for frequently changing resources 508 | Handle errors gracefully with clear error messages 509 | Consider pagination for large resource lists 510 | Cache resource contents when appropriate 511 | Validate URIs before processing 512 | Document your custom URI schemes 513 | ​ 514 | Security considerations 515 | When exposing resources: 516 | 517 | Validate all resource URIs 518 | Implement appropriate access controls 519 | Sanitize file paths to prevent directory traversal 520 | Be cautious with binary data handling 521 | Consider rate limiting for resource reads 522 | Audit resource access 523 | Encrypt sensitive data in transit 524 | Validate MIME types 525 | Implement timeouts for long-running reads 526 | Handle resource cleanup appropriately 527 | Was this page helpful? 528 | 529 | ### 530 | 531 | Here is information on MCP Tools 532 | 533 | ### 534 | 535 | Concepts 536 | Tools 537 | Enable LLMs to perform actions through your server 538 | 539 | Tools are a powerful primitive in the Model Context Protocol (MCP) that enable servers to expose executable functionality to clients. Through tools, LLMs can interact with external systems, perform computations, and take actions in the real world. 540 | 541 | Tools are designed to be model-controlled, meaning that tools are exposed from servers to clients with the intention of the AI model being able to automatically invoke them (with a human in the loop to grant approval). 542 | 543 | ​ 544 | Overview 545 | Tools in MCP allow servers to expose executable functions that can be invoked by clients and used by LLMs to perform actions. Key aspects of tools include: 546 | 547 | Discovery: Clients can list available tools through the tools/list endpoint 548 | Invocation: Tools are called using the tools/call endpoint, where servers perform the requested operation and return results 549 | Flexibility: Tools can range from simple calculations to complex API interactions 550 | Like resources, tools are identified by unique names and can include descriptions to guide their usage. However, unlike resources, tools represent dynamic operations that can modify state or interact with external systems. 551 | 552 | ​ 553 | Tool definition structure 554 | Each tool is defined with the following structure: 555 | 556 | { 557 | name: string; // Unique identifier for the tool 558 | description?: string; // Human-readable description 559 | inputSchema: { // JSON Schema for the tool's parameters 560 | type: "object", 561 | properties: { ... } // Tool-specific parameters 562 | } 563 | } 564 | ​ 565 | Implementing tools 566 | Here’s an example of implementing a basic tool in an MCP server: 567 | 568 | TypeScript 569 | Python 570 | 571 | const server = new Server({ 572 | name: "example-server", 573 | version: "1.0.0" 574 | }, { 575 | capabilities: { 576 | tools: {} 577 | } 578 | }); 579 | 580 | // Define available tools 581 | server.setRequestHandler(ListToolsRequestSchema, async () => { 582 | return { 583 | tools: [{ 584 | name: "calculate_sum", 585 | description: "Add two numbers together", 586 | inputSchema: { 587 | type: "object", 588 | properties: { 589 | a: { type: "number" }, 590 | b: { type: "number" } 591 | }, 592 | required: ["a", "b"] 593 | } 594 | }] 595 | }; 596 | }); 597 | 598 | // Handle tool execution 599 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 600 | if (request.params.name === "calculate_sum") { 601 | const { a, b } = request.params.arguments; 602 | return { 603 | content: [ 604 | { 605 | type: "text", 606 | text: String(a + b) 607 | } 608 | ] 609 | }; 610 | } 611 | throw new Error("Tool not found"); 612 | }); 613 | ​ 614 | Example tool patterns 615 | Here are some examples of types of tools that a server could provide: 616 | 617 | ​ 618 | System operations 619 | Tools that interact with the local system: 620 | 621 | { 622 | name: "execute_command", 623 | description: "Run a shell command", 624 | inputSchema: { 625 | type: "object", 626 | properties: { 627 | command: { type: "string" }, 628 | args: { type: "array", items: { type: "string" } } 629 | } 630 | } 631 | } 632 | ​ 633 | API integrations 634 | Tools that wrap external APIs: 635 | 636 | { 637 | name: "github_create_issue", 638 | description: "Create a GitHub issue", 639 | inputSchema: { 640 | type: "object", 641 | properties: { 642 | title: { type: "string" }, 643 | body: { type: "string" }, 644 | labels: { type: "array", items: { type: "string" } } 645 | } 646 | } 647 | } 648 | ​ 649 | Data processing 650 | Tools that transform or analyze data: 651 | 652 | { 653 | name: "analyze_csv", 654 | description: "Analyze a CSV file", 655 | inputSchema: { 656 | type: "object", 657 | properties: { 658 | filepath: { type: "string" }, 659 | operations: { 660 | type: "array", 661 | items: { 662 | enum: ["sum", "average", "count"] 663 | } 664 | } 665 | } 666 | } 667 | } 668 | ​ 669 | Best practices 670 | When implementing tools: 671 | 672 | Provide clear, descriptive names and descriptions 673 | Use detailed JSON Schema definitions for parameters 674 | Include examples in tool descriptions to demonstrate how the model should use them 675 | Implement proper error handling and validation 676 | Use progress reporting for long operations 677 | Keep tool operations focused and atomic 678 | Document expected return value structures 679 | Implement proper timeouts 680 | Consider rate limiting for resource-intensive operations 681 | Log tool usage for debugging and monitoring 682 | ​ 683 | Security considerations 684 | When exposing tools: 685 | 686 | ​ 687 | Input validation 688 | Validate all parameters against the schema 689 | Sanitize file paths and system commands 690 | Validate URLs and external identifiers 691 | Check parameter sizes and ranges 692 | Prevent command injection 693 | ​ 694 | Access control 695 | Implement authentication where needed 696 | Use appropriate authorization checks 697 | Audit tool usage 698 | Rate limit requests 699 | Monitor for abuse 700 | ​ 701 | Error handling 702 | Don’t expose internal errors to clients 703 | Log security-relevant errors 704 | Handle timeouts appropriately 705 | Clean up resources after errors 706 | Validate return values 707 | ​ 708 | Tool discovery and updates 709 | MCP supports dynamic tool discovery: 710 | 711 | Clients can list available tools at any time 712 | Servers can notify clients when tools change using notifications/tools/list_changed 713 | Tools can be added or removed during runtime 714 | Tool definitions can be updated (though this should be done carefully) 715 | ​ 716 | Error handling 717 | Tool errors should be reported within the result object, not as MCP protocol-level errors. This allows the LLM to see and potentially handle the error. When a tool encounters an error: 718 | 719 | Set isError to true in the result 720 | Include error details in the content array 721 | Here’s an example of proper error handling for tools: 722 | 723 | TypeScript 724 | Python 725 | 726 | try { 727 | // Tool operation 728 | const result = performOperation(); 729 | return { 730 | content: [ 731 | { 732 | type: "text", 733 | text: `Operation successful: ${result}` 734 | } 735 | ] 736 | }; 737 | } catch (error) { 738 | return { 739 | isError: true, 740 | content: [ 741 | { 742 | type: "text", 743 | text: `Error: ${error.message}` 744 | } 745 | ] 746 | }; 747 | } 748 | This approach allows the LLM to see that an error occurred and potentially take corrective action or request human intervention. 749 | 750 | ​ 751 | Testing tools 752 | A comprehensive testing strategy for MCP tools should cover: 753 | 754 | Functional testing: Verify tools execute correctly with valid inputs and handle invalid inputs appropriately 755 | Integration testing: Test tool interaction with external systems using both real and mocked dependencies 756 | Security testing: Validate authentication, authorization, input sanitization, and rate limiting 757 | Performance testing: Check behavior under load, timeout handling, and resource cleanup 758 | Error handling: Ensure tools properly report errors through the MCP protocol and clean up resources 759 | 760 | ### 761 | 762 | Here is information on MCP Prompts 763 | 764 | ### 765 | 766 | Concepts 767 | Prompts 768 | Create reusable prompt templates and workflows 769 | 770 | Prompts enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs. They provide a powerful way to standardize and share common LLM interactions. 771 | 772 | Prompts are designed to be user-controlled, meaning they are exposed from servers to clients with the intention of the user being able to explicitly select them for use. 773 | 774 | ​ 775 | Overview 776 | Prompts in MCP are predefined templates that can: 777 | 778 | Accept dynamic arguments 779 | Include context from resources 780 | Chain multiple interactions 781 | Guide specific workflows 782 | Surface as UI elements (like slash commands) 783 | ​ 784 | Prompt structure 785 | Each prompt is defined with: 786 | 787 | { 788 | name: string; // Unique identifier for the prompt 789 | description?: string; // Human-readable description 790 | arguments?: [ // Optional list of arguments 791 | { 792 | name: string; // Argument identifier 793 | description?: string; // Argument description 794 | required?: boolean; // Whether argument is required 795 | } 796 | ] 797 | } 798 | ​ 799 | Discovering prompts 800 | Clients can discover available prompts through the prompts/list endpoint: 801 | 802 | // Request 803 | { 804 | method: "prompts/list" 805 | } 806 | 807 | // Response 808 | { 809 | prompts: [ 810 | { 811 | name: "analyze-code", 812 | description: "Analyze code for potential improvements", 813 | arguments: [ 814 | { 815 | name: "language", 816 | description: "Programming language", 817 | required: true 818 | } 819 | ] 820 | } 821 | ] 822 | } 823 | ​ 824 | Using prompts 825 | To use a prompt, clients make a prompts/get request: 826 | 827 | // Request 828 | { 829 | method: "prompts/get", 830 | params: { 831 | name: "analyze-code", 832 | arguments: { 833 | language: "python" 834 | } 835 | } 836 | } 837 | 838 | // Response 839 | { 840 | description: "Analyze Python code for potential improvements", 841 | messages: [ 842 | { 843 | role: "user", 844 | content: { 845 | type: "text", 846 | text: "Please analyze the following Python code for potential improvements:\n\n`python\ndef calculate_sum(numbers):\n total = 0\n for num in numbers:\n total = total + num\n return total\n\nresult = calculate_sum([1, 2, 3, 4, 5])\nprint(result)\n`" 847 | } 848 | } 849 | ] 850 | } 851 | ​ 852 | Dynamic prompts 853 | Prompts can be dynamic and include: 854 | 855 | ​ 856 | Embedded resource context 857 | 858 | { 859 | "name": "analyze-project", 860 | "description": "Analyze project logs and code", 861 | "arguments": [ 862 | { 863 | "name": "timeframe", 864 | "description": "Time period to analyze logs", 865 | "required": true 866 | }, 867 | { 868 | "name": "fileUri", 869 | "description": "URI of code file to review", 870 | "required": true 871 | } 872 | ] 873 | } 874 | When handling the prompts/get request: 875 | 876 | { 877 | "messages": [ 878 | { 879 | "role": "user", 880 | "content": { 881 | "type": "text", 882 | "text": "Analyze these system logs and the code file for any issues:" 883 | } 884 | }, 885 | { 886 | "role": "user", 887 | "content": { 888 | "type": "resource", 889 | "resource": { 890 | "uri": "logs://recent?timeframe=1h", 891 | "text": "[2024-03-14 15:32:11] ERROR: Connection timeout in network.py:127\n[2024-03-14 15:32:15] WARN: Retrying connection (attempt 2/3)\n[2024-03-14 15:32:20] ERROR: Max retries exceeded", 892 | "mimeType": "text/plain" 893 | } 894 | } 895 | }, 896 | { 897 | "role": "user", 898 | "content": { 899 | "type": "resource", 900 | "resource": { 901 | "uri": "file:///path/to/code.py", 902 | "text": "def connect_to_service(timeout=30):\n retries = 3\n for attempt in range(retries):\n try:\n return establish_connection(timeout)\n except TimeoutError:\n if attempt == retries - 1:\n raise\n time.sleep(5)\n\ndef establish_connection(timeout):\n # Connection implementation\n pass", 903 | "mimeType": "text/x-python" 904 | } 905 | } 906 | } 907 | ] 908 | } 909 | ​ 910 | Multi-step workflows 911 | 912 | const debugWorkflow = { 913 | name: "debug-error", 914 | async getMessages(error: string) { 915 | return [ 916 | { 917 | role: "user", 918 | content: { 919 | type: "text", 920 | text: `Here's an error I'm seeing: ${error}` 921 | } 922 | }, 923 | { 924 | role: "assistant", 925 | content: { 926 | type: "text", 927 | text: "I'll help analyze this error. What have you tried so far?" 928 | } 929 | }, 930 | { 931 | role: "user", 932 | content: { 933 | type: "text", 934 | text: "I've tried restarting the service, but the error persists." 935 | } 936 | } 937 | ]; 938 | } 939 | }; 940 | ​ 941 | Example implementation 942 | Here’s a complete example of implementing prompts in an MCP server: 943 | 944 | TypeScript 945 | Python 946 | 947 | import { Server } from "@modelcontextprotocol/sdk/server"; 948 | import { 949 | ListPromptsRequestSchema, 950 | GetPromptRequestSchema 951 | } from "@modelcontextprotocol/sdk/types"; 952 | 953 | const PROMPTS = { 954 | "git-commit": { 955 | name: "git-commit", 956 | description: "Generate a Git commit message", 957 | arguments: [ 958 | { 959 | name: "changes", 960 | description: "Git diff or description of changes", 961 | required: true 962 | } 963 | ] 964 | }, 965 | "explain-code": { 966 | name: "explain-code", 967 | description: "Explain how code works", 968 | arguments: [ 969 | { 970 | name: "code", 971 | description: "Code to explain", 972 | required: true 973 | }, 974 | { 975 | name: "language", 976 | description: "Programming language", 977 | required: false 978 | } 979 | ] 980 | } 981 | }; 982 | 983 | const server = new Server({ 984 | name: "example-prompts-server", 985 | version: "1.0.0" 986 | }, { 987 | capabilities: { 988 | prompts: {} 989 | } 990 | }); 991 | 992 | // List available prompts 993 | server.setRequestHandler(ListPromptsRequestSchema, async () => { 994 | return { 995 | prompts: Object.values(PROMPTS) 996 | }; 997 | }); 998 | 999 | // Get specific prompt 1000 | server.setRequestHandler(GetPromptRequestSchema, async (request) => { 1001 | const prompt = PROMPTS[request.params.name]; 1002 | if (!prompt) { 1003 | throw new Error(`Prompt not found: ${request.params.name}`); 1004 | } 1005 | 1006 | if (request.params.name === "git-commit") { 1007 | return { 1008 | messages: [ 1009 | { 1010 | role: "user", 1011 | content: { 1012 | type: "text", 1013 | text: `Generate a concise but descriptive commit message for these changes:\n\n${request.params.arguments?.changes}` 1014 | } 1015 | } 1016 | ] 1017 | }; 1018 | } 1019 | 1020 | if (request.params.name === "explain-code") { 1021 | const language = request.params.arguments?.language || "Unknown"; 1022 | return { 1023 | messages: [ 1024 | { 1025 | role: "user", 1026 | content: { 1027 | type: "text", 1028 | text: `Explain how this ${language} code works:\n\n${request.params.arguments?.code}` 1029 | } 1030 | } 1031 | ] 1032 | }; 1033 | } 1034 | 1035 | throw new Error("Prompt implementation not found"); 1036 | }); 1037 | ​ 1038 | Best practices 1039 | When implementing prompts: 1040 | 1041 | Use clear, descriptive prompt names 1042 | Provide detailed descriptions for prompts and arguments 1043 | Validate all required arguments 1044 | Handle missing arguments gracefully 1045 | Consider versioning for prompt templates 1046 | Cache dynamic content when appropriate 1047 | Implement error handling 1048 | Document expected argument formats 1049 | Consider prompt composability 1050 | Test prompts with various inputs 1051 | ​ 1052 | UI integration 1053 | Prompts can be surfaced in client UIs as: 1054 | 1055 | Slash commands 1056 | Quick actions 1057 | Context menu items 1058 | Command palette entries 1059 | Guided workflows 1060 | Interactive forms 1061 | ​ 1062 | Updates and changes 1063 | Servers can notify clients about prompt changes: 1064 | 1065 | Server capability: prompts.listChanged 1066 | Notification: notifications/prompts/list_changed 1067 | Client re-fetches prompt list 1068 | ​ 1069 | Security considerations 1070 | When implementing prompts: 1071 | 1072 | Validate all arguments 1073 | Sanitize user input 1074 | Consider rate limiting 1075 | Implement access controls 1076 | Audit prompt usage 1077 | Handle sensitive data appropriately 1078 | Validate generated content 1079 | Implement timeouts 1080 | Consider prompt injection risks 1081 | Document security requirements 1082 | 1083 | ### 1084 | 1085 | Here is information for MCP Transport 1086 | 1087 | ### 1088 | 1089 | Transports 1090 | Learn about MCP’s communication mechanisms 1091 | 1092 | Transports in the Model Context Protocol (MCP) provide the foundation for communication between clients and servers. A transport handles the underlying mechanics of how messages are sent and received. 1093 | 1094 | ​ 1095 | Message Format 1096 | MCP uses JSON-RPC 2.0 as its wire format. The transport layer is responsible for converting MCP protocol messages into JSON-RPC format for transmission and converting received JSON-RPC messages back into MCP protocol messages. 1097 | 1098 | There are three types of JSON-RPC messages used: 1099 | 1100 | ​ 1101 | Requests 1102 | 1103 | { 1104 | jsonrpc: "2.0", 1105 | id: number | string, 1106 | method: string, 1107 | params?: object 1108 | } 1109 | ​ 1110 | Responses 1111 | 1112 | { 1113 | jsonrpc: "2.0", 1114 | id: number | string, 1115 | result?: object, 1116 | error?: { 1117 | code: number, 1118 | message: string, 1119 | data?: unknown 1120 | } 1121 | } 1122 | ​ 1123 | Notifications 1124 | 1125 | { 1126 | jsonrpc: "2.0", 1127 | method: string, 1128 | params?: object 1129 | } 1130 | ​ 1131 | Built-in Transport Types 1132 | MCP includes two standard transport implementations: 1133 | 1134 | ​ 1135 | Standard Input/Output (stdio) 1136 | The stdio transport enables communication through standard input and output streams. This is particularly useful for local integrations and command-line tools. 1137 | 1138 | Use stdio when: 1139 | 1140 | Building command-line tools 1141 | Implementing local integrations 1142 | Needing simple process communication 1143 | Working with shell scripts 1144 | TypeScript (Server) 1145 | TypeScript (Client) 1146 | Python (Server) 1147 | Python (Client) 1148 | 1149 | const server = new Server({ 1150 | name: "example-server", 1151 | version: "1.0.0" 1152 | }, { 1153 | capabilities: {} 1154 | }); 1155 | 1156 | const transport = new StdioServerTransport(); 1157 | await server.connect(transport); 1158 | ​ 1159 | Server-Sent Events (SSE) 1160 | SSE transport enables server-to-client streaming with HTTP POST requests for client-to-server communication. 1161 | 1162 | Use SSE when: 1163 | 1164 | Only server-to-client streaming is needed 1165 | Working with restricted networks 1166 | Implementing simple updates 1167 | TypeScript (Server) 1168 | TypeScript (Client) 1169 | Python (Server) 1170 | Python (Client) 1171 | 1172 | const server = new Server({ 1173 | name: "example-server", 1174 | version: "1.0.0" 1175 | }, { 1176 | capabilities: {} 1177 | }); 1178 | 1179 | const transport = new SSEServerTransport("/message", response); 1180 | await server.connect(transport); 1181 | ​ 1182 | Custom Transports 1183 | MCP makes it easy to implement custom transports for specific needs. Any transport implementation just needs to conform to the Transport interface: 1184 | 1185 | You can implement custom transports for: 1186 | 1187 | Custom network protocols 1188 | Specialized communication channels 1189 | Integration with existing systems 1190 | Performance optimization 1191 | TypeScript 1192 | Python 1193 | 1194 | interface Transport { 1195 | // Start processing messages 1196 | start(): Promise; 1197 | 1198 | // Send a JSON-RPC message 1199 | send(message: JSONRPCMessage): Promise; 1200 | 1201 | // Close the connection 1202 | close(): Promise; 1203 | 1204 | // Callbacks 1205 | onclose?: () => void; 1206 | onerror?: (error: Error) => void; 1207 | onmessage?: (message: JSONRPCMessage) => void; 1208 | } 1209 | ​ 1210 | Error Handling 1211 | Transport implementations should handle various error scenarios: 1212 | 1213 | Connection errors 1214 | Message parsing errors 1215 | Protocol errors 1216 | Network timeouts 1217 | Resource cleanup 1218 | Example error handling: 1219 | 1220 | TypeScript 1221 | Python 1222 | 1223 | class ExampleTransport implements Transport { 1224 | async start() { 1225 | try { 1226 | // Connection logic 1227 | } catch (error) { 1228 | this.onerror?.(new Error(`Failed to connect: ${error}`)); 1229 | throw error; 1230 | } 1231 | } 1232 | 1233 | async send(message: JSONRPCMessage) { 1234 | try { 1235 | // Sending logic 1236 | } catch (error) { 1237 | this.onerror?.(new Error(`Failed to send message: ${error}`)); 1238 | throw error; 1239 | } 1240 | } 1241 | } 1242 | ​ 1243 | Best Practices 1244 | When implementing or using MCP transport: 1245 | 1246 | Handle connection lifecycle properly 1247 | Implement proper error handling 1248 | Clean up resources on connection close 1249 | Use appropriate timeouts 1250 | Validate messages before sending 1251 | Log transport events for debugging 1252 | Implement reconnection logic when appropriate 1253 | Handle backpressure in message queues 1254 | Monitor connection health 1255 | Implement proper security measures 1256 | ​ 1257 | Security Considerations 1258 | When implementing transport: 1259 | 1260 | ​ 1261 | Authentication and Authorization 1262 | Implement proper authentication mechanisms 1263 | Validate client credentials 1264 | Use secure token handling 1265 | Implement authorization checks 1266 | ​ 1267 | Data Security 1268 | Use TLS for network transport 1269 | Encrypt sensitive data 1270 | Validate message integrity 1271 | Implement message size limits 1272 | Sanitize input data 1273 | ​ 1274 | Network Security 1275 | Implement rate limiting 1276 | Use appropriate timeouts 1277 | Handle denial of service scenarios 1278 | Monitor for unusual patterns 1279 | Implement proper firewall rules 1280 | ​ 1281 | Debugging Transport 1282 | Tips for debugging transport issues: 1283 | 1284 | Enable debug logging 1285 | Monitor message flow 1286 | Check connection states 1287 | Validate message formats 1288 | Test error scenarios 1289 | Use network analysis tools 1290 | Implement health checks 1291 | Monitor resource usage 1292 | Test edge cases 1293 | Use proper error tracking 1294 | 1295 | ### 1296 | 1297 | Here is information on MCP Sampling 1298 | 1299 | ### 1300 | 1301 | Sampling 1302 | Let your servers request completions from LLMs 1303 | 1304 | Sampling is a powerful MCP feature that allows servers to request LLM completions through the client, enabling sophisticated agentic behaviors while maintaining security and privacy. 1305 | 1306 | This feature of MCP is not yet supported in the Claude Desktop client. 1307 | 1308 | ​ 1309 | How sampling works 1310 | The sampling flow follows these steps: 1311 | 1312 | Server sends a sampling/createMessage request to the client 1313 | Client reviews the request and can modify it 1314 | Client samples from an LLM 1315 | Client reviews the completion 1316 | Client returns the result to the server 1317 | This human-in-the-loop design ensures users maintain control over what the LLM sees and generates. 1318 | 1319 | ​ 1320 | Message format 1321 | Sampling requests use a standardized message format: 1322 | 1323 | { 1324 | messages: [ 1325 | { 1326 | role: "user" | "assistant", 1327 | content: { 1328 | type: "text" | "image", 1329 | 1330 | // For text: 1331 | text?: string, 1332 | 1333 | // For images: 1334 | data?: string, // base64 encoded 1335 | mimeType?: string 1336 | } 1337 | } 1338 | 1339 | ], 1340 | modelPreferences?: { 1341 | hints?: [{ 1342 | name?: string // Suggested model name/family 1343 | }], 1344 | costPriority?: number, // 0-1, importance of minimizing cost 1345 | speedPriority?: number, // 0-1, importance of low latency 1346 | intelligencePriority?: number // 0-1, importance of capabilities 1347 | }, 1348 | systemPrompt?: string, 1349 | includeContext?: "none" | "thisServer" | "allServers", 1350 | temperature?: number, 1351 | maxTokens: number, 1352 | stopSequences?: string[], 1353 | metadata?: Record 1354 | } 1355 | ​ 1356 | Request parameters 1357 | ​ 1358 | Messages 1359 | The messages array contains the conversation history to send to the LLM. Each message has: 1360 | 1361 | role: Either “user” or “assistant” 1362 | content: The message content, which can be: 1363 | Text content with a text field 1364 | Image content with data (base64) and mimeType fields 1365 | ​ 1366 | Model preferences 1367 | The modelPreferences object allows servers to specify their model selection preferences: 1368 | 1369 | hints: Array of model name suggestions that clients can use to select an appropriate model: 1370 | 1371 | name: String that can match full or partial model names (e.g. “claude-3”, “sonnet”) 1372 | Clients may map hints to equivalent models from different providers 1373 | Multiple hints are evaluated in preference order 1374 | Priority values (0-1 normalized): 1375 | 1376 | costPriority: Importance of minimizing costs 1377 | speedPriority: Importance of low latency response 1378 | intelligencePriority: Importance of advanced model capabilities 1379 | Clients make the final model selection based on these preferences and their available models. 1380 | 1381 | ​ 1382 | System prompt 1383 | An optional systemPrompt field allows servers to request a specific system prompt. The client may modify or ignore this. 1384 | 1385 | ​ 1386 | Context inclusion 1387 | The includeContext parameter specifies what MCP context to include: 1388 | 1389 | "none": No additional context 1390 | "thisServer": Include context from the requesting server 1391 | "allServers": Include context from all connected MCP servers 1392 | The client controls what context is actually included. 1393 | 1394 | ​ 1395 | Sampling parameters 1396 | Fine-tune the LLM sampling with: 1397 | 1398 | temperature: Controls randomness (0.0 to 1.0) 1399 | maxTokens: Maximum tokens to generate 1400 | stopSequences: Array of sequences that stop generation 1401 | metadata: Additional provider-specific parameters 1402 | ​ 1403 | Response format 1404 | The client returns a completion result: 1405 | 1406 | { 1407 | model: string, // Name of the model used 1408 | stopReason?: "endTurn" | "stopSequence" | "maxTokens" | string, 1409 | role: "user" | "assistant", 1410 | content: { 1411 | type: "text" | "image", 1412 | text?: string, 1413 | data?: string, 1414 | mimeType?: string 1415 | } 1416 | } 1417 | ​ 1418 | Example request 1419 | Here’s an example of requesting sampling from a client: 1420 | 1421 | { 1422 | "method": "sampling/createMessage", 1423 | "params": { 1424 | "messages": [ 1425 | { 1426 | "role": "user", 1427 | "content": { 1428 | "type": "text", 1429 | "text": "What files are in the current directory?" 1430 | } 1431 | } 1432 | ], 1433 | "systemPrompt": "You are a helpful file system assistant.", 1434 | "includeContext": "thisServer", 1435 | "maxTokens": 100 1436 | } 1437 | } 1438 | ​ 1439 | Best practices 1440 | When implementing sampling: 1441 | 1442 | Always provide clear, well-structured prompts 1443 | Handle both text and image content appropriately 1444 | Set reasonable token limits 1445 | Include relevant context through includeContext 1446 | Validate responses before using them 1447 | Handle errors gracefully 1448 | Consider rate limiting sampling requests 1449 | Document expected sampling behavior 1450 | Test with various model parameters 1451 | Monitor sampling costs 1452 | ​ 1453 | Human in the loop controls 1454 | Sampling is designed with human oversight in mind: 1455 | 1456 | ​ 1457 | For prompts 1458 | Clients should show users the proposed prompt 1459 | Users should be able to modify or reject prompts 1460 | System prompts can be filtered or modified 1461 | Context inclusion is controlled by the client 1462 | ​ 1463 | For completions 1464 | Clients should show users the completion 1465 | Users should be able to modify or reject completions 1466 | Clients can filter or modify completions 1467 | Users control which model is used 1468 | ​ 1469 | Security considerations 1470 | When implementing sampling: 1471 | 1472 | Validate all message content 1473 | Sanitize sensitive information 1474 | Implement appropriate rate limits 1475 | Monitor sampling usage 1476 | Encrypt data in transit 1477 | Handle user data privacy 1478 | Audit sampling requests 1479 | Control cost exposure 1480 | Implement timeouts 1481 | Handle model errors gracefully 1482 | ​ 1483 | Common patterns 1484 | ​ 1485 | Agentic workflows 1486 | Sampling enables agentic patterns like: 1487 | 1488 | Reading and analyzing resources 1489 | Making decisions based on context 1490 | Generating structured data 1491 | Handling multi-step tasks 1492 | Providing interactive assistance 1493 | ​ 1494 | Context management 1495 | Best practices for context: 1496 | 1497 | Request minimal necessary context 1498 | Structure context clearly 1499 | Handle context size limits 1500 | Update context as needed 1501 | Clean up stale context 1502 | ​ 1503 | Error handling 1504 | Robust error handling should: 1505 | 1506 | Catch sampling failures 1507 | Handle timeout errors 1508 | Manage rate limits 1509 | Validate responses 1510 | Provide fallback behaviors 1511 | Log errors appropriately 1512 | ​ 1513 | Limitations 1514 | Be aware of these limitations: 1515 | 1516 | Sampling depends on client capabilities 1517 | Users control sampling behavior 1518 | Context size has limits 1519 | Rate limits may apply 1520 | Costs should be considered 1521 | Model availability varies 1522 | Response times vary 1523 | Not all content types supported 1524 | 1525 | ### 1526 | 1527 | Here is information on building an MCP Server with Node 1528 | 1529 | ### 1530 | 1531 | Quickstart 1532 | For Server Developers 1533 | Get started building your own server to use in Claude for Desktop and other clients. 1534 | 1535 | In this tutorial, we’ll build a simple MCP weather server and connect it to a host, Claude for Desktop. We’ll start with a basic setup, and then progress to more complex use cases. 1536 | 1537 | ​ 1538 | What we’ll be building 1539 | Many LLMs (including Claude) do not currently have the ability to fetch the forecast and severe weather alerts. Let’s use MCP to solve that! 1540 | 1541 | We’ll build a server that exposes two tools: get-alerts and get-forecast. Then we’ll connect the server to an MCP host (in this case, Claude for Desktop): 1542 | 1543 | Servers can connect to any client. We’ve chosen Claude for Desktop here for simplicity, but we also have guides on building your own client as well as a list of other clients here. 1544 | 1545 | Why Claude for Desktop and not Claude.ai? 1546 | 1547 | ​ 1548 | Core MCP Concepts 1549 | MCP servers can provide three main types of capabilities: 1550 | 1551 | Resources: File-like data that can be read by clients (like API responses or file contents) 1552 | Tools: Functions that can be called by the LLM (with user approval) 1553 | Prompts: Pre-written templates that help users accomplish specific tasks 1554 | This tutorial will primarily focus on tools. 1555 | 1556 | Python 1557 | Node 1558 | Let’s get started with building our weather server! You can find the complete code for what we’ll be building here. 1559 | 1560 | Prerequisite knowledge 1561 | This quickstart assumes you have familiarity with: 1562 | 1563 | TypeScript 1564 | LLMs like Claude 1565 | System requirements 1566 | For TypeScript, make sure you have the latest version of Node installed. 1567 | 1568 | Set up your environment 1569 | First, let’s install Node.js and npm if you haven’t already. You can download them from nodejs.org. Verify your Node.js installation: 1570 | 1571 | node --version 1572 | npm --version 1573 | For this tutorial, you’ll need Node.js version 16 or higher. 1574 | 1575 | Now, let’s create and set up our project: 1576 | 1577 | MacOS/Linux 1578 | 1579 | Windows 1580 | 1581 | # Create a new directory for our project 1582 | 1583 | mkdir weather 1584 | cd weather 1585 | 1586 | # Initialize a new npm project 1587 | 1588 | npm init -y 1589 | 1590 | # Install dependencies 1591 | 1592 | npm install @modelcontextprotocol/sdk zod 1593 | npm install -D @types/node typescript 1594 | 1595 | # Create our files 1596 | 1597 | mkdir src 1598 | touch src/index.ts 1599 | Update your package.json to add type: “module” and a build script: 1600 | 1601 | package.json 1602 | 1603 | { 1604 | "type": "module", 1605 | "bin": { 1606 | "weather": "./build/index.js" 1607 | }, 1608 | "scripts": { 1609 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 1610 | }, 1611 | "files": [ 1612 | "build" 1613 | ], 1614 | } 1615 | Create a tsconfig.json in the root of your project: 1616 | 1617 | tsconfig.json 1618 | 1619 | { 1620 | "compilerOptions": { 1621 | "target": "ES2022", 1622 | "module": "Node16", 1623 | "moduleResolution": "Node16", 1624 | "outDir": "./build", 1625 | "rootDir": "./src", 1626 | "strict": true, 1627 | "esModuleInterop": true, 1628 | "skipLibCheck": true, 1629 | "forceConsistentCasingInFileNames": true 1630 | }, 1631 | "include": ["src/**/*"], 1632 | "exclude": ["node_modules"] 1633 | } 1634 | Now let’s dive into building your server. 1635 | 1636 | Building your server 1637 | Importing packages 1638 | Add these to the top of your src/index.ts: 1639 | 1640 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 1641 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 1642 | import { 1643 | CallToolRequestSchema, 1644 | ListToolsRequestSchema, 1645 | } from "@modelcontextprotocol/sdk/types.js"; 1646 | import { z } from "zod"; 1647 | Setting up the instance 1648 | Then initialize the NWS API base URL, validation schemas, and server instance: 1649 | 1650 | const NWS_API_BASE = "https://api.weather.gov"; 1651 | const USER_AGENT = "weather-app/1.0"; 1652 | 1653 | // Define Zod schemas for validation 1654 | const AlertsArgumentsSchema = z.object({ 1655 | state: z.string().length(2), 1656 | }); 1657 | 1658 | const ForecastArgumentsSchema = z.object({ 1659 | latitude: z.number().min(-90).max(90), 1660 | longitude: z.number().min(-180).max(180), 1661 | }); 1662 | 1663 | // Create server instance 1664 | const server = new Server( 1665 | { 1666 | name: "weather", 1667 | version: "1.0.0", 1668 | }, 1669 | { 1670 | capabilities: { 1671 | tools: {}, 1672 | }, 1673 | } 1674 | ); 1675 | Implementing tool listing 1676 | We need to tell clients what tools are available. This server.setRequestHandler call will register this list for us: 1677 | 1678 | // List available tools 1679 | server.setRequestHandler(ListToolsRequestSchema, async () => { 1680 | return { 1681 | tools: [ 1682 | { 1683 | name: "get-alerts", 1684 | description: "Get weather alerts for a state", 1685 | inputSchema: { 1686 | type: "object", 1687 | properties: { 1688 | state: { 1689 | type: "string", 1690 | description: "Two-letter state code (e.g. CA, NY)", 1691 | }, 1692 | }, 1693 | required: ["state"], 1694 | }, 1695 | }, 1696 | { 1697 | name: "get-forecast", 1698 | description: "Get weather forecast for a location", 1699 | inputSchema: { 1700 | type: "object", 1701 | properties: { 1702 | latitude: { 1703 | type: "number", 1704 | description: "Latitude of the location", 1705 | }, 1706 | longitude: { 1707 | type: "number", 1708 | description: "Longitude of the location", 1709 | }, 1710 | }, 1711 | required: ["latitude", "longitude"], 1712 | }, 1713 | }, 1714 | ], 1715 | }; 1716 | }); 1717 | This defines our two tools: get-alerts and get-forecast. 1718 | 1719 | Helper functions 1720 | Next, let’s add our helper functions for querying and formatting the data from the National Weather Service API: 1721 | 1722 | // Helper function for making NWS API requests 1723 | async function makeNWSRequest(url: string): Promise { 1724 | const headers = { 1725 | "User-Agent": USER_AGENT, 1726 | Accept: "application/geo+json", 1727 | }; 1728 | 1729 | try { 1730 | const response = await fetch(url, { headers }); 1731 | if (!response.ok) { 1732 | throw new Error(`HTTP error! status: ${response.status}`); 1733 | } 1734 | return (await response.json()) as T; 1735 | } catch (error) { 1736 | console.error("Error making NWS request:", error); 1737 | return null; 1738 | } 1739 | } 1740 | 1741 | interface AlertFeature { 1742 | properties: { 1743 | event?: string; 1744 | areaDesc?: string; 1745 | severity?: string; 1746 | status?: string; 1747 | headline?: string; 1748 | }; 1749 | } 1750 | 1751 | // Format alert data 1752 | function formatAlert(feature: AlertFeature): string { 1753 | const props = feature.properties; 1754 | return [ 1755 | `Event: ${props.event || "Unknown"}`, 1756 | `Area: ${props.areaDesc || "Unknown"}`, 1757 | `Severity: ${props.severity || "Unknown"}`, 1758 | `Status: ${props.status || "Unknown"}`, 1759 | `Headline: ${props.headline || "No headline"}`, 1760 | "---", 1761 | ].join("\n"); 1762 | } 1763 | 1764 | interface ForecastPeriod { 1765 | name?: string; 1766 | temperature?: number; 1767 | temperatureUnit?: string; 1768 | windSpeed?: string; 1769 | windDirection?: string; 1770 | shortForecast?: string; 1771 | } 1772 | 1773 | interface AlertsResponse { 1774 | features: AlertFeature[]; 1775 | } 1776 | 1777 | interface PointsResponse { 1778 | properties: { 1779 | forecast?: string; 1780 | }; 1781 | } 1782 | 1783 | interface ForecastResponse { 1784 | properties: { 1785 | periods: ForecastPeriod[]; 1786 | }; 1787 | } 1788 | Implementing tool execution 1789 | The tool execution handler is responsible for actually executing the logic of each tool. Let’s add it: 1790 | 1791 | // Handle tool execution 1792 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 1793 | const { name, arguments: args } = request.params; 1794 | 1795 | try { 1796 | if (name === "get-alerts") { 1797 | const { state } = AlertsArgumentsSchema.parse(args); 1798 | const stateCode = state.toUpperCase(); 1799 | 1800 | const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`; 1801 | const alertsData = await makeNWSRequest(alertsUrl); 1802 | 1803 | if (!alertsData) { 1804 | return { 1805 | content: [ 1806 | { 1807 | type: "text", 1808 | text: "Failed to retrieve alerts data", 1809 | }, 1810 | ], 1811 | }; 1812 | } 1813 | 1814 | const features = alertsData.features || []; 1815 | if (features.length === 0) { 1816 | return { 1817 | content: [ 1818 | { 1819 | type: "text", 1820 | text: `No active alerts for ${stateCode}`, 1821 | }, 1822 | ], 1823 | }; 1824 | } 1825 | 1826 | const formattedAlerts = features.map(formatAlert).slice(0, 20) // only take the first 20 alerts; 1827 | const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join( 1828 | "\n" 1829 | )}`; 1830 | 1831 | return { 1832 | content: [ 1833 | { 1834 | type: "text", 1835 | text: alertsText, 1836 | }, 1837 | ], 1838 | }; 1839 | } else if (name === "get-forecast") { 1840 | const { latitude, longitude } = ForecastArgumentsSchema.parse(args); 1841 | 1842 | // Get grid point data 1843 | const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed( 1844 | 4 1845 | )},${longitude.toFixed(4)}`; 1846 | const pointsData = await makeNWSRequest(pointsUrl); 1847 | 1848 | if (!pointsData) { 1849 | return { 1850 | content: [ 1851 | { 1852 | type: "text", 1853 | text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`, 1854 | }, 1855 | ], 1856 | }; 1857 | } 1858 | 1859 | const forecastUrl = pointsData.properties?.forecast; 1860 | if (!forecastUrl) { 1861 | return { 1862 | content: [ 1863 | { 1864 | type: "text", 1865 | text: "Failed to get forecast URL from grid point data", 1866 | }, 1867 | ], 1868 | }; 1869 | } 1870 | 1871 | // Get forecast data 1872 | const forecastData = await makeNWSRequest(forecastUrl); 1873 | if (!forecastData) { 1874 | return { 1875 | content: [ 1876 | { 1877 | type: "text", 1878 | text: "Failed to retrieve forecast data", 1879 | }, 1880 | ], 1881 | }; 1882 | } 1883 | 1884 | const periods = forecastData.properties?.periods || []; 1885 | if (periods.length === 0) { 1886 | return { 1887 | content: [ 1888 | { 1889 | type: "text", 1890 | text: "No forecast periods available", 1891 | }, 1892 | ], 1893 | }; 1894 | } 1895 | 1896 | // Format forecast periods 1897 | const formattedForecast = periods.map((period: ForecastPeriod) => 1898 | [ 1899 | `${period.name || "Unknown"}:`, 1900 | `Temperature: ${period.temperature || "Unknown"}°${ 1901 | period.temperatureUnit || "F" 1902 | }`, 1903 | `Wind: ${period.windSpeed || "Unknown"} ${ 1904 | period.windDirection || "" 1905 | }`, 1906 | `${period.shortForecast || "No forecast available"}`, 1907 | "---", 1908 | ].join("\n") 1909 | ); 1910 | 1911 | const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join( 1912 | "\n" 1913 | )}`; 1914 | 1915 | return { 1916 | content: [ 1917 | { 1918 | type: "text", 1919 | text: forecastText, 1920 | }, 1921 | ], 1922 | }; 1923 | } else { 1924 | throw new Error(`Unknown tool: ${name}`); 1925 | } 1926 | 1927 | } catch (error) { 1928 | if (error instanceof z.ZodError) { 1929 | throw new Error( 1930 | `Invalid arguments: ${error.errors 1931 | .map((e) => `${e.path.join(".")}: ${e.message}`) 1932 | .join(", ")}` 1933 | ); 1934 | } 1935 | throw error; 1936 | } 1937 | }); 1938 | Running the server 1939 | Finally, implement the main function to run the server: 1940 | 1941 | // Start the server 1942 | async function main() { 1943 | const transport = new StdioServerTransport(); 1944 | await server.connect(transport); 1945 | console.error("Weather MCP Server running on stdio"); 1946 | } 1947 | 1948 | main().catch((error) => { 1949 | console.error("Fatal error in main():", error); 1950 | process.exit(1); 1951 | }); 1952 | Make sure to run npm run build to build your server! This is a very important step in getting your server to connect. 1953 | 1954 | Let’s now test your server from an existing MCP host, Claude for Desktop. 1955 | 1956 | Testing your server with Claude for Desktop 1957 | Claude for Desktop is not yet available on Linux. Linux users can proceed to the Building a client tutorial to build an MCP client that connects to the server we just built. 1958 | 1959 | First, make sure you have Claude for Desktop installed. You can install the latest version here. If you already have Claude for Desktop, make sure it’s updated to the latest version. 1960 | 1961 | We’ll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at ~/Library/Application Support/Claude/claude_desktop_config.json in a text editor. Make sure to create the file if it doesn’t exist. 1962 | 1963 | For example, if you have VS Code installed: 1964 | 1965 | MacOS/Linux 1966 | Windows 1967 | 1968 | code ~/Library/Application\ Support/Claude/claude_desktop_config.json 1969 | You’ll then add your servers in the mcpServers key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured. 1970 | 1971 | In this case, we’ll add our single weather server like so: 1972 | 1973 | MacOS/Linux 1974 | Windows 1975 | 1976 | Node 1977 | 1978 | { 1979 | "mcpServers": { 1980 | "weather": { 1981 | "command": "node", 1982 | "args": [ 1983 | "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js" 1984 | ] 1985 | } 1986 | } 1987 | } 1988 | This tells Claude for Desktop: 1989 | 1990 | There’s an MCP server named “weather” 1991 | Launch it by running node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js 1992 | Save the file, and restart Claude for Desktop. 1993 | 1994 | ​ 1995 | Test with commands 1996 | Let’s make sure Claude for Desktop is picking up the two tools we’ve exposed in our weather server. You can do this by looking for the hammer icon: 1997 | 1998 | After clicking on the hammer icon, you should see two tools listed: 1999 | 2000 | If your server isn’t being picked up by Claude for Desktop, proceed to the Troubleshooting section for debugging tips. 2001 | 2002 | If the hammer icon has shown up, you can now test your server by running the following commands in Claude for Desktop: 2003 | 2004 | What’s the weather in Sacramento? 2005 | What are the active weather alerts in Texas? 2006 | 2007 | Since this is the US National Weather service, the queries will only work for US locations. 2008 | 2009 | ​ 2010 | What’s happening under the hood 2011 | When you ask a question: 2012 | 2013 | The client sends your question to Claude 2014 | Claude analyzes the available tools and decides which one(s) to use 2015 | The client executes the chosen tool(s) through the MCP server 2016 | The results are sent back to Claude 2017 | Claude formulates a natural language response 2018 | The response is displayed to you! 2019 | 2020 | ### 2021 | --------------------------------------------------------------------------------