diff --git a/.github/workflows/mcpb-pack.yaml b/.github/workflows/mcpb-pack.yaml index 0cf6ba6..4a55992 100644 --- a/.github/workflows/mcpb-pack.yaml +++ b/.github/workflows/mcpb-pack.yaml @@ -29,6 +29,8 @@ jobs: mv manifest-minimal.tmp.json manifest-minimal.json jq --arg v "$pkg_version" '.version = $v' manifest-full.json > manifest-full.tmp.json mv manifest-full.tmp.json manifest-full.json + jq --arg v "$pkg_version" '.version = $v' manifest-code.json > manifest-code.tmp.json + mv manifest-code.tmp.json manifest-code.json - name: Build project run: npm run build @@ -53,9 +55,15 @@ jobs: mcpb pack mv "${current_dir}.mcpb" "${current_dir}-full.mcpb" + # Package code version + cp manifest-code.json manifest.json + mcpb pack + mv "${current_dir}.mcpb" "${current_dir}-code.mcpb" + # Set environment variables echo "MCPB_MINIMAL_FILENAME=${current_dir}-minimal.mcpb" >> $GITHUB_ENV echo "MCPB_FULL_FILENAME=${current_dir}-full.mcpb" >> $GITHUB_ENV + echo "MCPB_CODE_FILENAME=${current_dir}-code.mcpb" >> $GITHUB_ENV - name: Upload minimal release asset uses: svenstaro/upload-release-action@7027b7670c56b9473901daad1fb8a09ab534688e @@ -75,6 +83,15 @@ jobs: tag: ${{ github.ref }} overwrite: true + - name: Upload code release asset + uses: svenstaro/upload-release-action@7027b7670c56b9473901daad1fb8a09ab534688e + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: ${{ env.MCPB_CODE_FILENAME }} + asset_name: ${{ env.MCPB_CODE_FILENAME }} + tag: ${{ github.ref }} + overwrite: true + publish-mcp: needs: build-and-release runs-on: ubuntu-latest diff --git a/README.md b/README.md index e5f634a..29e763e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Postman supports the following tool configurations: * **Minimal** — (Default) Only includes essential tools for basic Postman operations This offers faster performance and simplifies use for those who only need basic Postman operations. Ideal for users who want to modify a single Postman elements, such as collections, workspaces, or environments. * **Full** — Includes all available Postman API tools (100+ tools). This configuration is ideal for users who engage in advanced collaboration and Postman's Enterprise features. +* **Code** — Includes tools to generate high-quality, well-organized client code from public and internal API definitions. This configuration is ideal for users who need to consume APIs or simply get context about APIs to their agents. For a complete list of the Postman MCP Server's tools, see the [Postman MCP Server collection](https://www.postman.com/postman/postman-public-workspace/collection/681dc649440b35935978b8b7). This collection offers both the remote [full](https://www.postman.com/postman/postman-public-workspace/mcp-request/6821a76b17ccb90a86df48d3) and [minimal](https://www.postman.com/postman/postman-public-workspace/mcp-request/689e1c635be722a98b723238) servers, and the [local server](https://www.postman.com/postman/postman-public-workspace/mcp-request/6866a655b36c67cc435b5033). @@ -19,8 +20,9 @@ Postman also offers servers as an [npm package](https://www.npmjs.com/package/@p * **Collection management** - Create and [tag](https://learning.postman.com/docs/collections/use-collections/collaborate-with-collections/#tag-a-collection) collections, update collection and request [documentation](https://learning.postman.com/docs/publishing-your-api/api-documentation-overview/), add [comments](https://learning.postman.com/docs/collaborating-in-postman/comments/), or perform actions across multiple collections without leaving your editor. * **Workspace and environment management** - Create [workspaces](https://learning.postman.com/docs/collaborating-in-postman/using-workspaces/overview/) and [environments](https://learning.postman.com/docs/sending-requests/variables/managing-environments/), plus manage your environment variables. * **Automatic spec creation** - Create [specs](https://learning.postman.com/docs/design-apis/specifications/overview/) from your code and use them to generate collections. +* **Client code generation** - Generate production-ready client code that consumes APIs following best practices and project conventions. The `code` toolset produces code that precisely matches your API definitions, organizes it into an intuitive tree structure mirroring your Postman collections and requests, and leverages example responses to create accurate response types and error handling. -Designed for developers who want to integrate their AI tools with Postman’s context and features. Supports quick natural language queries queries to advanced agent workflows. +Designed for developers who want to integrate their AI tools with Postman's context and features. Supports quick natural language queries to advanced agent workflows. ### Support for EU @@ -55,6 +57,7 @@ The remote Postman MCP Server is hosted by Postman over streamable HTTP and prov The remote server supports the following tool configurations: * **Minimal** — (Default) Only includes essential tools for basic Postman operations, available at `https://mcp.postman.com/minimal` and `https://mcp.eu.postman.com/minimal` for EU users. +* **Code** — Includes tools for searching public and internal API definitions and generating client code, available at `https://mcp.postman.com/code` and `https://mcp.eu.postman.com/code` for EU users. * **Full** — Includes all available Postman API tools (100+ tools), available at `https://mcp.postman.com/mcp` and `https://mcp.eu.postman.com/mcp` for EU users. ### Install in Cursor @@ -65,7 +68,7 @@ To install the remote Postman MCP Server in Cursor, click the install button. **Note:** Ensure that the Authorization header uses the `Bearer ` format. -By default, the server uses **Minimal** mode. To access **Full** mode, change the `url` value to `https://mcp.postman.com/mcp` in the `mcp.json` file. +By default, the server uses **Minimal** mode. To access **Full** mode, change the `url` value to `https://mcp.postman.com/mcp` in the `mcp.json` file. To access **Code** mode, change the value to `https://mcp.postman.com/code`. ### Install in Visual Studio Code @@ -73,7 +76,7 @@ By default, the server uses **Minimal** mode. To access **Full** mode, change th To install the remote Postman MCP Server in VS Code, click the install button or use the [Postman VS Code Extension](https://marketplace.visualstudio.com/items?itemName=Postman.postman-for-vscode). -By default, the server uses **Minimal** mode. To access **Full** mode, change the `url` value to `https://mcp.postman.com/mcp` in the `mcp.json` file. +By default, the server uses **Minimal** mode. To access **Full** mode, change the `url` value to `https://mcp.postman.com/mcp` in the `mcp.json` file. To access **Code** mode, change the value to `https://mcp.postman.com/code`. #### Manual configuration @@ -84,8 +87,7 @@ You can use the Postman MCP Server with MCP-compatible extensions in VS Code, su "servers": { "postman-api-http-server": { "type": "http", - "url": "https://mcp.postman.com/{minimal OR mcp}", - // Use "https://mcp.postman.com/mcp" for full or "https://mcp.postman.com/minimal" for minimal mode. + "url": "https://mcp.postman.com/{minimal OR code OR mcp}", // For the EU server, use the "https://mcp.eu.postman.com" URL. "headers": { "Authorization": "Bearer ${input:postman-api-key}" @@ -114,6 +116,12 @@ For **Minimal** mode: claude mcp add --transport http postman https://mcp.postman.com/minimal --header "Authorization: Bearer " ``` +For **Code** mode: + +```bash +claude mcp add --transport http postman https://mcp.postman.com/code --header "Authorization: Bearer " +``` + For **Full** mode: ```bash @@ -133,6 +141,7 @@ STDIO is a lightweight solution that's ideal for integration with editors and to The local server supports the following tool configurations: * **Minimal** — (Default) Only includes essential tools for basic Postman operations. +* **Code** — Includes tools for searching public and internal API definitions and generating client code * **Full** — Includes all available Postman API tools (100+ tools). Use the `--full` flag to enable this configuration. **Note:** Use the `--region` flag to specify the Postman API region (`us` or `eu`), or set the `POSTMAN_API_BASE_URL` environment variable directly. By default, the server uses the `us` option. @@ -144,7 +153,7 @@ The local server supports the following tool configurations: To install the local Postman MCP Server in VS Code, click the install button. -By default, the server uses **Full** mode. To access **Minimal** mode, remove the `--full` flag from the `mcp.json` configuration file. +By default, the server uses **Full** mode. To access **Minimal** mode, remove the `--full` flag from the `mcp.json` configuration file. To access **Code** mode, replace the `--full` flag with `--code`. #### Manual configuration @@ -158,7 +167,8 @@ You can manually integrate your MCP server with Cursor or VS Code to use it with "command": "npx", "args": [ "@postman/postman-mcp-server", - "--full" // (optional) Use this flag to enable full mode. + "--full", // (optional) Use this flag to enable full mode... + "--code", // (optional) ...OR this flag to enable code mode. "--region us" // (optional) Use this flag to specify the Postman API region (us or eu). Defaults to us. ], "env": { @@ -182,14 +192,15 @@ You can manually integrate your MCP server with Cursor or VS Code to use it with To install the local Postman MCP Server in Cursor, click the install button. -By default, the server uses **Full** mode. To access **Minimal** mode, remove the `--full` flag from the `mcp.json` configuration file. +By default, the server uses **Full** mode. To access **Minimal** mode, remove the `--full` flag from the `mcp.json` configuration file. To access **Code** mode, replace the `--full` flag with `--code`. ### Claude integration To integrate the MCP server with Claude, check the latest [Postman MCP Server release](https://github.com/postmanlabs/postman-mcp-server/releases) and get the `.mcpb` file. -* **Minimal** - `postman-api-mcp-minimal.mcpb` -* **Full** - `postman-api-mcp-full.mcpb` +* **Minimal** - `postman-mcp-server-minimal.mcpb` +* **Full** - `postman-mcp-server-full.mcpb` +* **Code** - `postman-mcp-server-code.mcpb` For more information, see the [Claude Desktop Extensions](https://www.anthropic.com/engineering/desktop-extensions) documentation. @@ -203,6 +214,12 @@ For **Minimal** mode: claude mcp add postman --env POSTMAN_API_KEY=YOUR_KEY -- npx @postman/postman-mcp-server@latest ``` +For **Code** mode: + +```bash +claude mcp add postman --env POSTMAN_API_KEY=YOUR_KEY -- npx @postman/postman-mcp-server@latest --code +``` + For **Full** mode: ```bash @@ -239,7 +256,6 @@ If you're migrating from Postman MCP Server version 1.x to 2.x, be aware of the ## Questions and support -* See the [Postman Agent Generator](https://postman.com/explore/agent-generator) page for updates and new capabilities. * See [Add your MCP requests to your collections](https://learning.postman.com/docs/postman-ai-agent-builder/mcp-requests/overview/) to learn how to use Postman to perform MCP requests. * Visit the [Postman Community](https://community.postman.com/) to share what you've built, ask questions, and get help. * You can connect to both the remote and local servers and test them using the [Postman MCP Server collection](https://www.postman.com/postman/postman-public-workspace/collection/681dc649440b35935978b8b7). diff --git a/dist/package.json b/dist/package.json index ed6b938..f8c3657 100644 --- a/dist/package.json +++ b/dist/package.json @@ -1,6 +1,6 @@ { "name": "@postman/postman-mcp-server", - "version": "2.4.9", + "version": "2.5.0", "description": "A simple MCP server to operate on the Postman API", "mcpName": "com.postman/postman-mcp-server", "main": "dist/src/index.js", @@ -27,21 +27,21 @@ "access": "public" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.18.1", - "dotenv": "^17.2.2", - "newman": "^6.2.1", + "@modelcontextprotocol/sdk": "^1.22.0", + "dotenv": "^17.2.3", + "newman": "^6.2.0", "uuid": "^13.0.0" }, "devDependencies": { - "@eslint/js": "^9.35.0", + "@eslint/js": "^9.39.1", "@types/node": "^24", - "eslint": "^9.35.0", + "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-unused-imports": "^4.2.0", + "eslint-plugin-unused-imports": "^4.3.0", "prettier": "^3.6.2", - "typescript": "^5.9.2", - "typescript-eslint": "^8.44.0", - "vitest": "^3.2.4" + "typescript": "^5.9.3", + "typescript-eslint": "^8.48.0", + "vitest": "^4.0.13" }, "engines": { "node": ">=20.0.0" diff --git a/dist/src/enabledResources.js b/dist/src/enabledResources.js index 6d0b5a3..0b15b2b 100644 --- a/dist/src/enabledResources.js +++ b/dist/src/enabledResources.js @@ -35,6 +35,7 @@ const full = [ 'getCollectionResponse', 'updateCollectionResponse', 'transferCollectionResponses', + 'runCollection', 'createCollectionComment', 'deleteCollectionComment', 'getCollectionComments', @@ -110,7 +111,7 @@ const full = [ 'deleteApiCollectionComment', 'deleteSpecFile', 'getEnabledTools', - 'runCollection', + 'searchPostmanElements', ]; const minimal = [ 'createCollection', @@ -154,9 +155,28 @@ const minimal = [ 'runCollection', 'getEnabledTools', ]; -const excludedFromGeneration = ['runCollection', 'getEnabledTools']; +const code = [ + 'getCodeGenerationInstructions', + 'getWorkspace', + 'getWorkspaces', + 'searchPostmanElements', + 'getCollectionRequest', + 'getCollectionResponse', + 'getCollectionFolder', + 'getAuthenticatedUser', + 'getCollectionMap', + 'getEnvironment', + 'getEnvironments', +]; +const excludedFromGeneration = [ + 'runCollection', + 'getEnabledTools', + 'getCodeGenerationInstructions', + 'getCollectionMap', +]; export const enabledResources = { full, minimal, + code, excludedFromGeneration, }; diff --git a/dist/src/index.js b/dist/src/index.js index 597b6a4..38f3222 100755 --- a/dist/src/index.js +++ b/dist/src/index.js @@ -97,6 +97,7 @@ let clientInfo = undefined; async function run() { const args = process.argv.slice(2); const useFull = args.includes('--full'); + const useCode = args.includes('--code'); const regionIndex = args.findIndex((arg) => arg === '--region'); if (regionIndex !== -1 && regionIndex + 1 < args.length) { const region = args[regionIndex + 1]; @@ -126,7 +127,8 @@ async function run() { }); const fullTools = allGeneratedTools.filter((t) => enabledResources.full.includes(t.method)); const minimalTools = allGeneratedTools.filter((t) => enabledResources.minimal.includes(t.method)); - const tools = useFull ? fullTools : minimalTools; + const codeTools = allGeneratedTools.filter((t) => enabledResources.code.includes(t.method)); + const tools = useCode ? codeTools : useFull ? fullTools : minimalTools; const server = new McpServer({ name: SERVER_NAME, version: APP_VERSION }); server.onerror = (error) => { const msg = String(error?.message || error); @@ -139,7 +141,7 @@ async function run() { }); const client = new PostmanAPIClient(apiKey); const serverContext = { - serverType: useFull ? 'full' : 'minimal', + serverType: useCode ? 'code' : useFull ? 'full' : 'minimal', availableTools: tools.map((t) => t.method), }; log('info', 'Registering tools with McpServer'); @@ -182,7 +184,8 @@ async function run() { } }; await server.connect(transport); - logBoth(server, 'info', `Server connected and ready: ${SERVER_NAME}@${APP_VERSION} with ${tools.length} tools (${useFull ? 'full' : 'minimal'})`); + const toolsetName = useCode ? 'code' : useFull ? 'full' : 'minimal'; + logBoth(server, 'info', `Server connected and ready: ${SERVER_NAME}@${APP_VERSION} with ${tools.length} tools (${toolsetName})`); } run().catch((error) => { log('error', 'Unhandled error during server execution', { diff --git a/dist/src/tools/getCodeGenerationInstructions.js b/dist/src/tools/getCodeGenerationInstructions.js new file mode 100644 index 0000000..a6852c1 --- /dev/null +++ b/dist/src/tools/getCodeGenerationInstructions.js @@ -0,0 +1,439 @@ +import { z } from 'zod'; +export const method = 'getCodeGenerationInstructions'; +export const description = `MANDATORY: You MUST call this tool BEFORE generating any code to call APIs. Call it BEFORE you start planning your approach. Do not web search anything about the API or how to write code to call it. + +This tool returns comprehensive step-by-step instructions for generating API client code from Postman collections, including which tools to call for gathering context, file structure, function design patterns, error handling, and language-specific conventions. +Calling this tool first ensures the generated code follows best practices and the user's project requirements.`; +export const parameters = z.object({}); +export const annotations = { + title: 'Get Code Generation Instructions', + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, +}; +const CODE_GENERATION_INSTRUCTIONS = `# API Client Code Generation Instructions + +These instructions guide you in generating idiomatic client code from Postman collections, organized in a clear structure that is easy to find and maintain. + +## Core Principles + +**Generate code for specific requests only:** Only generate client code for the individual requests the user indicates. Do not automatically generate code for an entire folder or collection—wait for the user to specify which requests they want client code for, or ask them. + +**Match the target project's language and style:** This is critical. Analyze the project's language, framework, structure, and conventions before generating any code. The examples in this document use JavaScript/TypeScript and Python, but you must generate code in whatever language the project uses and try to match the style and conventions of the project. Do not generate code in a different language than the project uses, regardless of examples shown, unless explicitly requested. + +**Follow an ordered workflow:** The instructions below provide a step-by-step process. Follow these steps in order. + +--- + +## Workflow Overview + +When the user requests code generation for a Postman request, or code generation for something that depends on a Postman request, follow this sequence: + +1. **Gather Context** - Fetch all necessary Postman data (collection, folders, request, responses, environments) +2. **Determine Base Directory** - Find or choose where generated code should live +3. **Plan File Structure** - Calculate slugs and file paths +4. **Set Up Variables** - Generate a variables file for collection and environment variables +5. **Generate Client Code** - Create the client function with proper structure +6. **Deduplicate and Extract Shared Code** - Consolidate common code into shared utilities +7. **Verify Quality** - Ensure code meets quality standards + +--- + +## Step 1: Gather Context + +Before generating code, gather all appropriate context. Use these MCP tools to fetch the necessary data IF it has not already been fetched: + +IMPORTANT: for ALL tools that accept an id, **use the full uid**. +The uid format is - and an example uid is 34229158-378697c9-3044-44b1-9a0e-1417194cee44, where +34229158 is the ownerId and 378697c9-3044-44b1-9a0e-1417194cee44 is the id. +When you encounter an id that has no ownerId, prepend the ownerId from the collection before using +it as a tool call argument. + +**Required:** + +- \`getCollectionRequest\` - Fetch the request you're generating code for +- \`getCollectionFolder\` - Fetch all parent folders recursively (they may contain docs and instructions that apply to the request) +- \`getCollectionMap\` - Fetch the collection map, which includes collection-level docs that may apply to the request +- \`getCollectionResponse\` - If the request has response examples, fetch each one to understand request/response permutations and shapes. Use this for: + - Creating response types in typed languages + - Adding response schema comments in untyped languages + - Understanding both success and error cases +- \`getEnvironments\` - Fetch all environments for the workspace +- \`getEnvironment\` - For each environment, fetch the full details to see what variables have been defined and have values + +**Important:** If you've already fetched this information earlier in the conversation, reuse it. Only make additional tool calls to fill gaps in context. + +**Important: Do not skip any required steps. Gather ALL required information +in Step 1 before moving to Step 2. Missing information will result in +incomplete code generation.** + +--- + +## Step 2: Determine Base Directory + +The base directory (\`baseDir\`) is where all generated API client code will be placed. + +**Discovery process:** + +1. Search the project for existing generated client code by looking for opening comments containing the words "postman code" + +2. If found, extract the base directory path from the location of these existing files to determine the established \`baseDir\` + +3. If no existing generated code is found, choose a new \`baseDir\` based on project conventions for where API client functions should live + - The leaf directory name should be \`postman\` + - Examples: \`src/postman\`, \`lib/postman\`, \`app/postman\` + +--- + +## Step 3: Plan File Structure + +### Directory Structure + +Organize generated code following this hierarchy: + +\`\`\` +/ + / + / + / + client. + / + client. + shared/ + (extracted types and utilities) +\`\`\` + +### File Path Per Request + +- Path pattern: \`////client.\` +- Do not use the request name in the filename; always use \`client.ts\`, \`client.js\`, \`client.py\`, etc. +- Each request gets its own directory named with its slug +- Export following existing project conventions + +### Slugification + +Convert any Postman object name into a filesystem and git-safe string: + +\`\`\`javascript +function createSlug(name) { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); +} +\`\`\` + +### Collision Handling + +If two sibling requests resolve to the same slug, append an index: \`-1\`, \`-2\`, \`-3\`, etc. + +--- + +## Step 4: Set Up Variables + +If variables exist at the collection level or in any environment, generate a variables file in the shared folder. + +### Variables File Location + +Place the variables file at: \`//shared/variables.\` + +Use the appropriate extension for the target language (\`.ts\`, \`.js\`, \`.py\`, etc.). + +### Variables File Structure + +Export a single object named \`variables\` that contains: + +- A \`collection\` key for collection-level variables +- A key for each environment (using the exact environment name from Postman) + +**Important:** +- Environment names can be anything and are used exactly as they appear in Postman +- Do not modify or normalize environment names +- Do not bind variables to environment variables here—this file reflects what's on Postman +- The caller of the generated API client is responsible for preparing variables to pass to the API clients, using data from the variables file or whatever it deems appropriate + +**Example (TypeScript):** + +\`\`\`typescript +export const variables = { + collection: { + apiVersion: 'v2', + retryAttempts: 3, + }, + "Production Environment": { + baseUrl: 'https://api.example.com', + apiKey: '', + }, + "Staging Environment": { + baseUrl: 'https://api-staging.example.com', + apiKey: '', + }, +}; +\`\`\` + +**Example (Python):** + +\`\`\`python +variables = { + 'collection': { + 'api_version': 'v2', + 'retry_attempts': 3, + }, + 'Production Environment': { + 'base_url': 'https://api.example.com', + 'api_key': '', + }, + 'Staging Environment': { + 'base_url': 'https://api-staging.example.com', + 'api_key': '', + }, +} +\`\`\` + +--- + +## Step 5: Generate Client Code + +For each request, generate a client file with the following components: + +### Opening Comment (Required) + +Every generated client file must include an opening comment at the top using language-appropriate comment syntax. + +**Required fields:** + +- The phrase "Generated by Postman Code" (for discovery) +- Collection name and Collection UID (grouped together) +- Full path showing folders and request name (e.g., "Folder1 > Subfolder > Request Name") +- Request UID +- Modified timestamp from the Postman request object (serves as a version indicator) + +**Example formats:** + +JavaScript/TypeScript: + +\`\`\`javascript +/** + * Generated by Postman Code + * + * Collection: Stripe API + * Collection UID: 12345678-1234-1234-1234-123456789abc + * + * Request: Payment Intents > Create Payment Intent + * Request UID: 87654321-4321-4321-4321-cba987654321 + * Request modified at: 2024-11-10T15:45:30.000Z + */ +\`\`\` + +Python: + +\`\`\`python +""" +Generated by Postman Code +Collection: Stripe API +Collection UID: 12345678-1234-1234-1234-123456789abc +Request: Payment Intents > Create Payment Intent +Request UID: 87654321-4321-4321-4321-cba987654321 +Request modified at: 2024-11-10T15:45:30.000Z +""" +\`\`\` + +### Client Function Implementation + +Generate a client function that implements these components: + +**Variable handling:** + +- Client functions should accept all variables they will use as specific function parameters +- Do not hardcode variable values in client functions +- The caller is responsible for: + - Selecting which environment to use from the variables object + - Merging collection-level and environment-level variables + - Binding any variables to environment variables or secrets + - Passing the merged variables to client functions + +**URL construction:** + +- Accept base URL and other URL-related variables as function parameters +- Substitute path parameters with function arguments +- Encode query parameters properly +- Build the complete URL from the base URL and path + +**Headers:** + +- Set headers per specification +- Never hardcode secrets; use environment variables or project secret helpers +- Follow project conventions for auth token handling + +**Request body:** + +- Serialize according to content type (JSON, form-data, urlencoded, etc.) +- Type appropriately in typed languages + +**Authentication:** + +- Implement exactly as defined in the request (bearer token, API key, basic auth, etc.) +- Always pull credentials from environment variables or project secret management +- Never hardcode credentials + +**Response handling:** + +- Parse and shape the payload if response information exists +- In typed languages, generate or reuse types for request/response shapes +- Implement explicit error handling for each response example in the Postman request + - If the request has a 404 response example, include specific handling for 404 + - Each documented error case should be explicitly caught and logged with appropriate context +- Follow project and any existing API client code conventions for error handling patterns around logging, exception classes, error codes, etc. + +**Error handling example:** + +\`\`\`javascript +// If Postman request has examples for 200, 404, 401, and 422 responses: +const response = await fetch(url, options); + +if (response.ok) { + return await response.json(); +} + +// Explicit handling for each documented error response +switch (response.status) { + case 404: + console.error('Resource not found'); + throw new NotFoundError(await response.json()); + + case 401: + console.error('Authentication failed'); + throw new UnauthorizedError(await response.json()); + + case 422: + console.error('Validation failed'); + throw new ValidationError(await response.json()); + + default: + console.error('Unexpected error'); + throw new Error(await response.text()); +} +\`\`\` + +**Documentation:** + +- Add standard docstrings (JSDoc, TSDoc, Python docstrings, etc.) +- Include description from Postman request +- Document all parameters, return types, and possible errors + +### Follow Existing Patterns + +**Match project conventions:** + +- Mirror the Postman workspace structure: collection → folders → requests +- Follow existing naming conventions in the \`baseDir\` +- Match casing style (camelCase, PascalCase, snake_case) +- Match export style (named exports, default exports, module.exports) +- Match directory layout conventions +- Use existing HTTP helpers if present in the project +- Follow existing error handling patterns +- Match existing auth implementation patterns +- Use environment variables consistently with project conventions +- Match documentation style already present + +**Only deviate from existing patterns if explicitly requested by the user.** + +### Language-Specific Guidelines + +**JavaScript/TypeScript:** + +- Use modern async/await syntax unless the project conventions dictate otherwise +- Use \`fetch\` or existing HTTP client in the project +- If TS, proper TypeScript types for all functions, parameters, and return values +- If JS, proper JSDoc comments that contain request and response shapes +- Use JSDoc or TSDoc comments in general for all functions, parameters, and return values + +**Python:** + +- Use \`requests\` library or existing HTTP client +- Type hints for all functions +- Proper docstrings (Google, NumPy, or project style) +- Follow PEP 8 conventions + +**Other languages:** + +- Follow idiomatic patterns for the language +- Use standard HTTP libraries +- Apply language-specific naming conventions +- Use appropriate documentation format; follow guidelines above for TS/JS and adapt to the language + +--- + +## Step 6: Deduplicate and Extract Shared Code + +After generating all requested client files, consolidate duplicated code within each collection. + +### Detection + +Identify duplicated types, interfaces, and utility functions within the same collection. + +### Extraction Location + +Extract shared code to: \`//shared/\` + +This location is used for: + +- Common types and interfaces +- Shared utility functions +- Common authentication helpers +- Reusable validation logic + +### Update Imports + +After extracting shared code: + +- Modify generated clients to import from shared locations +- Maintain correct relative paths +- Ensure all imports resolve correctly + +### Cross-Collection Sharing + +- Keep helpers within a collection by default (e.g., Stripe and Slack collections stay separate) +- Only share code across collections when explicitly requested by the user + +### Example Structure + +\`\`\` +/ + github-api/ + shared/ + types.ts + auth.ts + users/ + get-user/ + client.ts + list-users/ + client.ts + repos/ + get-repo/ + client.ts + list-repos/ + client.ts +\`\`\` + +--- + +## Step 7: Verify Quality Standards + +Ensure all generated code meets these standards: + +- All generated code must be lintable and follow project linting rules +- Code should be production-ready, not placeholder or example code +- Error handling should be robust and informative +- Type safety should be maintained in typed languages +- Security best practices must be followed (no hardcoded secrets, proper input validation)`; +export async function handler(_args, _extra) { + return { + content: [ + { + type: 'text', + text: CODE_GENERATION_INSTRUCTIONS, + }, + ], + }; +} diff --git a/dist/src/tools/getCollectionMap.js b/dist/src/tools/getCollectionMap.js new file mode 100644 index 0000000..0508fa8 --- /dev/null +++ b/dist/src/tools/getCollectionMap.js @@ -0,0 +1,101 @@ +import { z } from 'zod'; +import { asMcpError, McpError } from './utils/toolHelpers.js'; +export const method = 'getCollectionMap'; +export const description = `Get a Postman collection map with metadata and a complete recursive index of all folders and requests. Response includes collection metadata and description. Response includes itemRefs property (name and id only) instead of the full item array. After calling, present the collection summary and ask the user where they\'d like to explore next, calling getCollectionFolder and/or getCollectionRequest tools in parallel to get more data quickly. + Once you've called this tool, DO NOT call searchPostmanElements to find items in or related to this collection. Instead, use the map in itemRefs. + Only use searchPostmanElements to find the collection where a request may be. Then, stay in the collection and don't use the search. + When using the getCollectionRequest tool to look up request data, omit the populate parameter to avoid getting all response examples + back at once (can be very large). Instead, use the response ids from the return value and call getCollectionResponse for each one. + Prepend the collection's ownerId to the front of each response id when passing it to getCollectionResponse. This is the first part of the collection uid. + Infer the response schema from that information and remember it. Omit the raw response examples from the conversation going forward.`; +export const parameters = z.object({ + collectionId: z + .string() + .describe('The collection ID must be in the form - (e.g. 12345-33823532ab9e41c9b6fd12d0fd459b8b).'), + access_key: z + .string() + .describe("A collection's read-only access key. Using this query parameter does not require an API key to call the endpoint.") + .optional(), +}); +export const annotations = { + title: 'Get Postman Collection Map', + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, +}; +function buildItemRefs(items) { + if (!Array.isArray(items) || items.length === 0) { + return undefined; + } + return items.map((item) => { + const itemId = item.uid || item.id || ''; + const itemRef = { + name: item.name || '', + id: itemId, + }; + if (item.item && Array.isArray(item.item)) { + const nestedRefs = buildItemRefs(item.item); + if (nestedRefs) { + itemRef.itemRefs = nestedRefs; + } + } + return itemRef; + }); +} +export async function handler(args, extra) { + try { + const endpoint = `/collections/${args.collectionId}`; + const query = new URLSearchParams(); + if (args.access_key !== undefined) + query.set('access_key', String(args.access_key)); + const url = query.toString() ? `${endpoint}?${query.toString()}` : endpoint; + const options = { + headers: extra.headers, + }; + const result = await extra.client.get(url, options); + if (typeof result === 'string') { + return { + content: [ + { + type: 'text', + text: result, + }, + ], + }; + } + const response = result; + if (response.collection) { + const { item, ...collectionWithoutItems } = response.collection; + const itemRefs = buildItemRefs(item); + const processedResponse = { + ...response, + collection: { + ...collectionWithoutItems, + ...(itemRefs && { itemRefs }), + }, + }; + return { + content: [ + { + type: 'text', + text: JSON.stringify(processedResponse, null, 2), + }, + ], + }; + } + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; + } + catch (e) { + if (e instanceof McpError) { + throw e; + } + throw asMcpError(e); + } +} diff --git a/dist/src/tools/searchPostmanElements.js b/dist/src/tools/searchPostmanElements.js new file mode 100644 index 0000000..0a2ea8e --- /dev/null +++ b/dist/src/tools/searchPostmanElements.js @@ -0,0 +1,69 @@ +import { z } from 'zod'; +import { asMcpError, McpError } from './utils/toolHelpers.js'; +export const method = 'searchPostmanElements'; +export const description = 'Searches for Postman elements in the public network.\n\n**When to Use This Tool:**\n- When the user asks for a specific named request (e.g., "find PayPal requests", "search for Stripe API requests")\n- When the user explicitly wants to search the public network\n- Do NOT use this for searching the user\'s own workspaces or collections (use getCollections or getWorkspaces instead)\n\n**Search Scope:**\n- Only searches the public network (public workspaces and collections)\n- Does not search private workspaces, team workspaces, or personal collections\n- Currently supports searching for requests (entityType: "requests")\n'; +export const parameters = z.object({ + entityType: z + .literal('requests') + .describe('The type of Postman [entity](https://learning.postman.com/docs/getting-started/basics/postman-elements/) (element) to search for. At this time, this only accepts the `requests` value.'), + q: z + .string() + .min(1) + .max(512) + .describe('The query used to search for Postman elements.') + .optional(), + publisherIsVerified: z + .boolean() + .describe('Filter the search results to only return entities from publishers [verified](https://learning.postman.com/docs/collaborating-in-postman/public-api-network/verify-your-team/) by Postman.') + .optional(), + nextCursor: z + .string() + .describe('The cursor to get the next set of results in the paginated response. If you pass an invalid value, the API returns empty results.') + .optional(), + limit: z + .number() + .int() + .gte(1) + .lte(10) + .describe('The max number of search results returned in the response.') + .default(10), +}); +export const annotations = { + title: 'Searches for Postman elements in the public network.\n\n**When to Use This Tool:**\n- When the user asks for a specific named request (e.g., "find PayPal requests", "search for Stripe API requests")\n- When the user explicitly wants to search the public network\n- Do NOT use this for searching the user\'s own workspaces or collections (use getCollections or getWorkspaces instead)\n\n**Search Scope:**\n- Only searches the public network (public workspaces and collections)\n- Does not search private workspaces, team workspaces, or personal collections\n- Currently supports searching for requests (entityType: "requests")\n', + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, +}; +export async function handler(args, extra) { + try { + const endpoint = `/search/${args.entityType}`; + const query = new URLSearchParams(); + if (args.q !== undefined) + query.set('q', String(args.q)); + if (args.publisherIsVerified !== undefined) + query.set('publisherIsVerified', String(args.publisherIsVerified)); + if (args.nextCursor !== undefined) + query.set('nextCursor', String(args.nextCursor)); + if (args.limit !== undefined) + query.set('limit', String(args.limit)); + const url = query.toString() ? `${endpoint}?${query.toString()}` : endpoint; + const options = { + headers: extra.headers, + }; + const result = await extra.client.get(url, options); + return { + content: [ + { + type: 'text', + text: `${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}`, + }, + ], + }; + } + catch (e) { + if (e instanceof McpError) { + throw e; + } + throw asMcpError(e); + } +} diff --git a/manifest-code.json b/manifest-code.json new file mode 100644 index 0000000..34de0a2 --- /dev/null +++ b/manifest-code.json @@ -0,0 +1,67 @@ +{ + "manifest_version": "0.3", + "version": "2.5.0", + "name": "postman-mcp-server-code", + "display_name": "Postman MCP Server (Code)", + "description": "Search for APIs and generate client code from Postman collections.", + "long_description": "Search public and internal API definitions and generate high-quality, well-organized client code for making API requests. Provide API context to AI agents for any task. All secured with your Postman API key.", + "author": { + "name": "Postman, Inc.", + "email": "help@postman.com", + "url": "https://www.postman.com" + }, + "repository": { + "type": "git", + "url": "https://github.com/postmanlabs/postman-mcp-server" + }, + "homepage": "https://github.com/postmanlabs/postman-mcp-server", + "documentation": "https://learning.postman.com/docs/developer/postman-api/postman-mcp-server/overview", + "support": "https://github.com/postmanlabs/postman-api-mcp/issues", + "icon": "icon.png", + "server": { + "type": "node", + "entry_point": "dist/src/index.js", + "mcp_config": { + "command": "node", + "args": [ + "${__dirname}/dist/src/index.js", + "--code" + ], + "env": { + "POSTMAN_API_KEY": "${user_config.postman_api_key}" + } + } + }, + "keywords": [ + "postman", + "api", + "mcp", + "postman-api", + "collections", + "code-generation", + "codegen", + "sdk" + ], + "license": "Apache-2.0", + "user_config": { + "postman_api_key": { + "type": "string", + "title": "Postman API Key", + "description": "A valid Postman API key used to authenticate requests.", + "sensitive": true, + "required": true + } + }, + "compatibility": { + "claude_desktop": ">=0.10.0", + "platforms": [ + "darwin", + "win32", + "linux" + ], + "runtimes": { + "node": ">=20.0.0" + } + }, + "tools_generated": true +} diff --git a/manifest-full.json b/manifest-full.json index 5027de8..e305065 100644 --- a/manifest-full.json +++ b/manifest-full.json @@ -1,6 +1,6 @@ { "manifest_version": "0.3", - "version": "2.4.9", + "version": "2.5.0", "name": "postman-mcp-server-full", "display_name": "Postman MCP Server (Full)", "description": "Connect your AI to your APIs on Postman.", diff --git a/manifest-minimal.json b/manifest-minimal.json index a3b30b9..376c220 100644 --- a/manifest-minimal.json +++ b/manifest-minimal.json @@ -1,6 +1,6 @@ { "manifest_version": "0.3", - "version": "2.4.9", + "version": "2.5.0", "name": "postman-mcp-server-minimal", "display_name": "Postman MCP Server (Minimal)", "description": "Connect your AI to your APIs on Postman.", diff --git a/package-lock.json b/package-lock.json index fecf40e..9296002 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@postman/postman-mcp-server", - "version": "2.4.9", + "version": "2.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@postman/postman-mcp-server", - "version": "2.4.9", + "version": "2.5.0", "license": "Apache-2.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.22.0", diff --git a/package.json b/package.json index 9cc7aa2..4da0f2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@postman/postman-mcp-server", - "version": "2.4.9", + "version": "2.5.0", "description": "A simple MCP server to operate on the Postman API", "mcpName": "com.postman/postman-mcp-server", "main": "dist/src/index.js", diff --git a/scripts/release.js b/scripts/release.js index beb0c82..c590c54 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -91,6 +91,7 @@ try { updateManifest('manifest-full.json'); updateManifest('manifest-minimal.json'); + updateManifest('manifest-code.json'); // Build mcpb packages locally to calculate SHA256 hashes console.log('📦 Building mcpb packages for SHA256 calculation...'); @@ -120,6 +121,12 @@ try { execSync('mcpb pack', { stdio: 'inherit' }); execSync(`mv "${currentDir}.mcpb" "postman-mcp-server-full.mcpb"`, { stdio: 'inherit' }); + // Package code version + console.log('📦 Packaging code version...'); + execSync('cp manifest-code.json manifest.json', { stdio: 'inherit' }); + execSync('mcpb pack', { stdio: 'inherit' }); + execSync(`mv "${currentDir}.mcpb" "postman-mcp-server-code.mcpb"`, { stdio: 'inherit' }); + // Restore manifest.json (optional, or delete it) execSync('rm manifest.json', { stdio: 'inherit' }); @@ -143,7 +150,8 @@ try { console.log('🔐 Calculating SHA256 hashes for mcpb packages...'); const mcpbFiles = [ { name: 'postman-mcp-server-minimal.mcpb', path: 'postman-mcp-server-minimal.mcpb' }, - { name: 'postman-mcp-server-full.mcpb', path: 'postman-mcp-server-full.mcpb' } + { name: 'postman-mcp-server-full.mcpb', path: 'postman-mcp-server-full.mcpb' }, + { name: 'postman-mcp-server-code.mcpb', path: 'postman-mcp-server-code.mcpb' } ]; const mcpbPackages = serverJson.packages.filter(pkg => pkg.registryType === 'mcpb'); @@ -168,7 +176,7 @@ try { // Clean up mcpb files (they'll be rebuilt by GitHub Action) console.log('🧹 Cleaning up local mcpb packages...'); - execSync('rm -f postman-mcp-server-minimal.mcpb postman-mcp-server-full.mcpb', { stdio: 'inherit' }); + execSync('rm -f postman-mcp-server-minimal.mcpb postman-mcp-server-full.mcpb postman-mcp-server-code.mcpb', { stdio: 'inherit' }); // Commit and tag console.log('📤 Committing and tagging...'); diff --git a/server.json b/server.json index db0dcd5..1b1d025 100644 --- a/server.json +++ b/server.json @@ -6,12 +6,12 @@ "url": "https://github.com/postmanlabs/postman-mcp-server", "source": "github" }, - "version": "2.4.9", + "version": "2.5.0", "packages": [ { "registryType": "npm", "identifier": "@postman/postman-mcp-server", - "version": "2.4.9", + "version": "2.5.0", "transport": { "type": "stdio" }, @@ -26,8 +26,8 @@ }, { "registryType": "mcpb", - "identifier": "https://github.com/postmanlabs/postman-mcp-server/releases/download/v2.4.9/postman-mcp-server-minimal.mcpb", - "fileSha256": "64fe7c9c2b3d193de095a948f1ae7c8e5bcc8b4dbf2bfb5fb3713df85f4e28e9", + "identifier": "https://github.com/postmanlabs/postman-mcp-server/releases/download/v2.5.0/postman-mcp-server-minimal.mcpb", + "fileSha256": "d5e0d0c5be703968ee2668ca546b2378150f587c3b6481a07d8c54bfd309cf45", "transport": { "type": "stdio" }, @@ -42,8 +42,24 @@ }, { "registryType": "mcpb", - "identifier": "https://github.com/postmanlabs/postman-mcp-server/releases/download/v2.4.9/postman-mcp-server-full.mcpb", - "fileSha256": "14a777004ee020b3527b2c01ce662da94f1cdc0cf52dc83b3881971533f655f7", + "identifier": "https://github.com/postmanlabs/postman-mcp-server/releases/download/v2.5.0/postman-mcp-server-full.mcpb", + "fileSha256": "4248611256735cb5055162d07f07dcef7ec619d238961c59e486023b40822129", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "A valid Postman API key.", + "isRequired": true, + "isSecret": true, + "name": "POSTMAN_API_KEY" + } + ] + }, + { + "registryType": "mcpb", + "identifier": "https://github.com/postmanlabs/postman-mcp-server/releases/download/v2.5.0/postman-mcp-server-code.mcpb", + "fileSha256": "7a8598af854218c860f2c15aadb41fdd8893aa50736c44231d43677f10457cbc", "transport": { "type": "stdio" }, diff --git a/src/enabledResources.ts b/src/enabledResources.ts index cff2f6d..70b0f0f 100644 --- a/src/enabledResources.ts +++ b/src/enabledResources.ts @@ -45,6 +45,9 @@ const full = [ 'updateCollectionResponse', 'transferCollectionResponses', + // Collection Runner + 'runCollection', + // Comments 'createCollectionComment', 'deleteCollectionComment', @@ -146,6 +149,7 @@ const full = [ 'deleteApiCollectionComment', 'deleteSpecFile', 'getEnabledTools', + 'searchPostmanElements', ] as const; const minimal = [ @@ -191,10 +195,30 @@ const minimal = [ 'getEnabledTools', ] as const; -const excludedFromGeneration = ['runCollection', 'getEnabledTools'] as const; +const code = [ + 'getCodeGenerationInstructions', + 'getWorkspace', + 'getWorkspaces', + 'searchPostmanElements', + 'getCollectionRequest', + 'getCollectionResponse', + 'getCollectionFolder', + 'getAuthenticatedUser', + 'getCollectionMap', + 'getEnvironment', + 'getEnvironments', +] as const; + +const excludedFromGeneration = [ + 'runCollection', + 'getEnabledTools', + 'getCodeGenerationInstructions', + 'getCollectionMap', +] as const; export const enabledResources = { full, minimal, + code, excludedFromGeneration, }; diff --git a/src/index.ts b/src/index.ts index 89e14e9..d3b5112 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,6 +65,7 @@ function logBoth( type FullResourceMethod = (typeof enabledResources.full)[number]; type MinimalResourceMethod = (typeof enabledResources.minimal)[number]; +type CodeResourceMethod = (typeof enabledResources.code)[number]; type EnabledResourceMethod = FullResourceMethod; interface ToolModule { @@ -156,6 +157,7 @@ let clientInfo: InitializeRequest['params']['clientInfo'] | undefined = undefine async function run() { const args = process.argv.slice(2); const useFull = args.includes('--full'); + const useCode = args.includes('--code'); const regionIndex = args.findIndex((arg) => arg === '--region'); if (regionIndex !== -1 && regionIndex + 1 < args.length) { @@ -191,7 +193,10 @@ async function run() { const minimalTools = allGeneratedTools.filter((t) => enabledResources.minimal.includes(t.method as MinimalResourceMethod) ); - const tools = useFull ? fullTools : minimalTools; + const codeTools = allGeneratedTools.filter((t) => + enabledResources.code.includes(t.method as CodeResourceMethod) + ); + const tools = useCode ? codeTools : useFull ? fullTools : minimalTools; // Create McpServer instance const server = new McpServer({ name: SERVER_NAME, version: APP_VERSION }); @@ -213,7 +218,7 @@ async function run() { // Create server context that will be passed to all tools const serverContext: ServerContext = { - serverType: useFull ? 'full' : 'minimal', + serverType: useCode ? 'code' : useFull ? 'full' : 'minimal', availableTools: tools.map((t) => t.method), }; @@ -271,10 +276,11 @@ async function run() { } }; await server.connect(transport); + const toolsetName = useCode ? 'code' : useFull ? 'full' : 'minimal'; logBoth( server, 'info', - `Server connected and ready: ${SERVER_NAME}@${APP_VERSION} with ${tools.length} tools (${useFull ? 'full' : 'minimal'})` + `Server connected and ready: ${SERVER_NAME}@${APP_VERSION} with ${tools.length} tools (${toolsetName})` ); } diff --git a/src/tools/getCodeGenerationInstructions.ts b/src/tools/getCodeGenerationInstructions.ts new file mode 100644 index 0000000..216a5b5 --- /dev/null +++ b/src/tools/getCodeGenerationInstructions.ts @@ -0,0 +1,450 @@ +import { z } from 'zod'; +import { PostmanAPIClient } from '../clients/postman.js'; +import { IsomorphicHeaders, CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import { ServerContext } from './utils/toolHelpers.js'; + +export const method = 'getCodeGenerationInstructions'; +export const description = `MANDATORY: You MUST call this tool BEFORE generating any code to call APIs. Call it BEFORE you start planning your approach. Do not web search anything about the API or how to write code to call it. + +This tool returns comprehensive step-by-step instructions for generating API client code from Postman collections, including which tools to call for gathering context, file structure, function design patterns, error handling, and language-specific conventions. +Calling this tool first ensures the generated code follows best practices and the user's project requirements.`; + +export const parameters = z.object({}); + +export const annotations = { + title: 'Get Code Generation Instructions', + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, +}; + +const CODE_GENERATION_INSTRUCTIONS = `# API Client Code Generation Instructions + +These instructions guide you in generating idiomatic client code from Postman collections, organized in a clear structure that is easy to find and maintain. + +## Core Principles + +**Generate code for specific requests only:** Only generate client code for the individual requests the user indicates. Do not automatically generate code for an entire folder or collection—wait for the user to specify which requests they want client code for, or ask them. + +**Match the target project's language and style:** This is critical. Analyze the project's language, framework, structure, and conventions before generating any code. The examples in this document use JavaScript/TypeScript and Python, but you must generate code in whatever language the project uses and try to match the style and conventions of the project. Do not generate code in a different language than the project uses, regardless of examples shown, unless explicitly requested. + +**Follow an ordered workflow:** The instructions below provide a step-by-step process. Follow these steps in order. + +--- + +## Workflow Overview + +When the user requests code generation for a Postman request, or code generation for something that depends on a Postman request, follow this sequence: + +1. **Gather Context** - Fetch all necessary Postman data (collection, folders, request, responses, environments) +2. **Determine Base Directory** - Find or choose where generated code should live +3. **Plan File Structure** - Calculate slugs and file paths +4. **Set Up Variables** - Generate a variables file for collection and environment variables +5. **Generate Client Code** - Create the client function with proper structure +6. **Deduplicate and Extract Shared Code** - Consolidate common code into shared utilities +7. **Verify Quality** - Ensure code meets quality standards + +--- + +## Step 1: Gather Context + +Before generating code, gather all appropriate context. Use these MCP tools to fetch the necessary data IF it has not already been fetched: + +IMPORTANT: for ALL tools that accept an id, **use the full uid**. +The uid format is - and an example uid is 34229158-378697c9-3044-44b1-9a0e-1417194cee44, where +34229158 is the ownerId and 378697c9-3044-44b1-9a0e-1417194cee44 is the id. +When you encounter an id that has no ownerId, prepend the ownerId from the collection before using +it as a tool call argument. + +**Required:** + +- \`getCollectionRequest\` - Fetch the request you're generating code for +- \`getCollectionFolder\` - Fetch all parent folders recursively (they may contain docs and instructions that apply to the request) +- \`getCollectionMap\` - Fetch the collection map, which includes collection-level docs that may apply to the request +- \`getCollectionResponse\` - If the request has response examples, fetch each one to understand request/response permutations and shapes. Use this for: + - Creating response types in typed languages + - Adding response schema comments in untyped languages + - Understanding both success and error cases +- \`getEnvironments\` - Fetch all environments for the workspace +- \`getEnvironment\` - For each environment, fetch the full details to see what variables have been defined and have values + +**Important:** If you've already fetched this information earlier in the conversation, reuse it. Only make additional tool calls to fill gaps in context. + +**Important: Do not skip any required steps. Gather ALL required information +in Step 1 before moving to Step 2. Missing information will result in +incomplete code generation.** + +--- + +## Step 2: Determine Base Directory + +The base directory (\`baseDir\`) is where all generated API client code will be placed. + +**Discovery process:** + +1. Search the project for existing generated client code by looking for opening comments containing the words "postman code" + +2. If found, extract the base directory path from the location of these existing files to determine the established \`baseDir\` + +3. If no existing generated code is found, choose a new \`baseDir\` based on project conventions for where API client functions should live + - The leaf directory name should be \`postman\` + - Examples: \`src/postman\`, \`lib/postman\`, \`app/postman\` + +--- + +## Step 3: Plan File Structure + +### Directory Structure + +Organize generated code following this hierarchy: + +\`\`\` +/ + / + / + / + client. + / + client. + shared/ + (extracted types and utilities) +\`\`\` + +### File Path Per Request + +- Path pattern: \`////client.\` +- Do not use the request name in the filename; always use \`client.ts\`, \`client.js\`, \`client.py\`, etc. +- Each request gets its own directory named with its slug +- Export following existing project conventions + +### Slugification + +Convert any Postman object name into a filesystem and git-safe string: + +\`\`\`javascript +function createSlug(name) { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); +} +\`\`\` + +### Collision Handling + +If two sibling requests resolve to the same slug, append an index: \`-1\`, \`-2\`, \`-3\`, etc. + +--- + +## Step 4: Set Up Variables + +If variables exist at the collection level or in any environment, generate a variables file in the shared folder. + +### Variables File Location + +Place the variables file at: \`//shared/variables.\` + +Use the appropriate extension for the target language (\`.ts\`, \`.js\`, \`.py\`, etc.). + +### Variables File Structure + +Export a single object named \`variables\` that contains: + +- A \`collection\` key for collection-level variables +- A key for each environment (using the exact environment name from Postman) + +**Important:** +- Environment names can be anything and are used exactly as they appear in Postman +- Do not modify or normalize environment names +- Do not bind variables to environment variables here—this file reflects what's on Postman +- The caller of the generated API client is responsible for preparing variables to pass to the API clients, using data from the variables file or whatever it deems appropriate + +**Example (TypeScript):** + +\`\`\`typescript +export const variables = { + collection: { + apiVersion: 'v2', + retryAttempts: 3, + }, + "Production Environment": { + baseUrl: 'https://api.example.com', + apiKey: '', + }, + "Staging Environment": { + baseUrl: 'https://api-staging.example.com', + apiKey: '', + }, +}; +\`\`\` + +**Example (Python):** + +\`\`\`python +variables = { + 'collection': { + 'api_version': 'v2', + 'retry_attempts': 3, + }, + 'Production Environment': { + 'base_url': 'https://api.example.com', + 'api_key': '', + }, + 'Staging Environment': { + 'base_url': 'https://api-staging.example.com', + 'api_key': '', + }, +} +\`\`\` + +--- + +## Step 5: Generate Client Code + +For each request, generate a client file with the following components: + +### Opening Comment (Required) + +Every generated client file must include an opening comment at the top using language-appropriate comment syntax. + +**Required fields:** + +- The phrase "Generated by Postman Code" (for discovery) +- Collection name and Collection UID (grouped together) +- Full path showing folders and request name (e.g., "Folder1 > Subfolder > Request Name") +- Request UID +- Modified timestamp from the Postman request object (serves as a version indicator) + +**Example formats:** + +JavaScript/TypeScript: + +\`\`\`javascript +/** + * Generated by Postman Code + * + * Collection: Stripe API + * Collection UID: 12345678-1234-1234-1234-123456789abc + * + * Request: Payment Intents > Create Payment Intent + * Request UID: 87654321-4321-4321-4321-cba987654321 + * Request modified at: 2024-11-10T15:45:30.000Z + */ +\`\`\` + +Python: + +\`\`\`python +""" +Generated by Postman Code +Collection: Stripe API +Collection UID: 12345678-1234-1234-1234-123456789abc +Request: Payment Intents > Create Payment Intent +Request UID: 87654321-4321-4321-4321-cba987654321 +Request modified at: 2024-11-10T15:45:30.000Z +""" +\`\`\` + +### Client Function Implementation + +Generate a client function that implements these components: + +**Variable handling:** + +- Client functions should accept all variables they will use as specific function parameters +- Do not hardcode variable values in client functions +- The caller is responsible for: + - Selecting which environment to use from the variables object + - Merging collection-level and environment-level variables + - Binding any variables to environment variables or secrets + - Passing the merged variables to client functions + +**URL construction:** + +- Accept base URL and other URL-related variables as function parameters +- Substitute path parameters with function arguments +- Encode query parameters properly +- Build the complete URL from the base URL and path + +**Headers:** + +- Set headers per specification +- Never hardcode secrets; use environment variables or project secret helpers +- Follow project conventions for auth token handling + +**Request body:** + +- Serialize according to content type (JSON, form-data, urlencoded, etc.) +- Type appropriately in typed languages + +**Authentication:** + +- Implement exactly as defined in the request (bearer token, API key, basic auth, etc.) +- Always pull credentials from environment variables or project secret management +- Never hardcode credentials + +**Response handling:** + +- Parse and shape the payload if response information exists +- In typed languages, generate or reuse types for request/response shapes +- Implement explicit error handling for each response example in the Postman request + - If the request has a 404 response example, include specific handling for 404 + - Each documented error case should be explicitly caught and logged with appropriate context +- Follow project and any existing API client code conventions for error handling patterns around logging, exception classes, error codes, etc. + +**Error handling example:** + +\`\`\`javascript +// If Postman request has examples for 200, 404, 401, and 422 responses: +const response = await fetch(url, options); + +if (response.ok) { + return await response.json(); +} + +// Explicit handling for each documented error response +switch (response.status) { + case 404: + console.error('Resource not found'); + throw new NotFoundError(await response.json()); + + case 401: + console.error('Authentication failed'); + throw new UnauthorizedError(await response.json()); + + case 422: + console.error('Validation failed'); + throw new ValidationError(await response.json()); + + default: + console.error('Unexpected error'); + throw new Error(await response.text()); +} +\`\`\` + +**Documentation:** + +- Add standard docstrings (JSDoc, TSDoc, Python docstrings, etc.) +- Include description from Postman request +- Document all parameters, return types, and possible errors + +### Follow Existing Patterns + +**Match project conventions:** + +- Mirror the Postman workspace structure: collection → folders → requests +- Follow existing naming conventions in the \`baseDir\` +- Match casing style (camelCase, PascalCase, snake_case) +- Match export style (named exports, default exports, module.exports) +- Match directory layout conventions +- Use existing HTTP helpers if present in the project +- Follow existing error handling patterns +- Match existing auth implementation patterns +- Use environment variables consistently with project conventions +- Match documentation style already present + +**Only deviate from existing patterns if explicitly requested by the user.** + +### Language-Specific Guidelines + +**JavaScript/TypeScript:** + +- Use modern async/await syntax unless the project conventions dictate otherwise +- Use \`fetch\` or existing HTTP client in the project +- If TS, proper TypeScript types for all functions, parameters, and return values +- If JS, proper JSDoc comments that contain request and response shapes +- Use JSDoc or TSDoc comments in general for all functions, parameters, and return values + +**Python:** + +- Use \`requests\` library or existing HTTP client +- Type hints for all functions +- Proper docstrings (Google, NumPy, or project style) +- Follow PEP 8 conventions + +**Other languages:** + +- Follow idiomatic patterns for the language +- Use standard HTTP libraries +- Apply language-specific naming conventions +- Use appropriate documentation format; follow guidelines above for TS/JS and adapt to the language + +--- + +## Step 6: Deduplicate and Extract Shared Code + +After generating all requested client files, consolidate duplicated code within each collection. + +### Detection + +Identify duplicated types, interfaces, and utility functions within the same collection. + +### Extraction Location + +Extract shared code to: \`//shared/\` + +This location is used for: + +- Common types and interfaces +- Shared utility functions +- Common authentication helpers +- Reusable validation logic + +### Update Imports + +After extracting shared code: + +- Modify generated clients to import from shared locations +- Maintain correct relative paths +- Ensure all imports resolve correctly + +### Cross-Collection Sharing + +- Keep helpers within a collection by default (e.g., Stripe and Slack collections stay separate) +- Only share code across collections when explicitly requested by the user + +### Example Structure + +\`\`\` +/ + github-api/ + shared/ + types.ts + auth.ts + users/ + get-user/ + client.ts + list-users/ + client.ts + repos/ + get-repo/ + client.ts + list-repos/ + client.ts +\`\`\` + +--- + +## Step 7: Verify Quality Standards + +Ensure all generated code meets these standards: + +- All generated code must be lintable and follow project linting rules +- Code should be production-ready, not placeholder or example code +- Error handling should be robust and informative +- Type safety should be maintained in typed languages +- Security best practices must be followed (no hardcoded secrets, proper input validation)`; + +export async function handler( + _args: z.infer, + _extra: { client: PostmanAPIClient; headers?: IsomorphicHeaders; serverContext?: ServerContext } +): Promise { + return { + content: [ + { + type: 'text', + text: CODE_GENERATION_INSTRUCTIONS, + }, + ], + }; +} diff --git a/src/tools/getCollectionMap.ts b/src/tools/getCollectionMap.ts new file mode 100644 index 0000000..5e5df31 --- /dev/null +++ b/src/tools/getCollectionMap.ts @@ -0,0 +1,127 @@ +import { z } from 'zod'; +import { PostmanAPIClient } from '../clients/postman.js'; +import { IsomorphicHeaders, CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import { ServerContext, asMcpError, McpError } from './utils/toolHelpers.js'; + +export const method = 'getCollectionMap'; +export const description = `Get a Postman collection map with metadata and a complete recursive index of all folders and requests. Response includes collection metadata and description. Response includes itemRefs property (name and id only) instead of the full item array. After calling, present the collection summary and ask the user where they\'d like to explore next, calling getCollectionFolder and/or getCollectionRequest tools in parallel to get more data quickly. + Once you've called this tool, DO NOT call searchPostmanElements to find items in or related to this collection. Instead, use the map in itemRefs. + Only use searchPostmanElements to find the collection where a request may be. Then, stay in the collection and don't use the search. + When using the getCollectionRequest tool to look up request data, omit the populate parameter to avoid getting all response examples + back at once (can be very large). Instead, use the response ids from the return value and call getCollectionResponse for each one. + Prepend the collection's ownerId to the front of each response id when passing it to getCollectionResponse. This is the first part of the collection uid. + Infer the response schema from that information and remember it. Omit the raw response examples from the conversation going forward.`; + +export const parameters = z.object({ + collectionId: z + .string() + .describe( + 'The collection ID must be in the form - (e.g. 12345-33823532ab9e41c9b6fd12d0fd459b8b).' + ), + access_key: z + .string() + .describe( + "A collection's read-only access key. Using this query parameter does not require an API key to call the endpoint." + ) + .optional(), +}); + +export const annotations = { + title: 'Get Postman Collection Map', + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, +}; + +/** + * Build recursive itemRefs structure from collection items + * Uses uid instead of id when available + */ +function buildItemRefs(items: any[] | undefined): any[] | undefined { + if (!Array.isArray(items) || items.length === 0) { + return undefined; + } + + return items.map((item: any) => { + const itemId = item.uid || item.id || ''; + + const itemRef: any = { + name: item.name || '', + id: itemId, + }; + + if (item.item && Array.isArray(item.item)) { + const nestedRefs = buildItemRefs(item.item); + if (nestedRefs) { + itemRef.itemRefs = nestedRefs; + } + } + + return itemRef; + }); +} + +export async function handler( + args: z.infer, + extra: { client: PostmanAPIClient; headers?: IsomorphicHeaders; serverContext?: ServerContext } +): Promise { + try { + const endpoint = `/collections/${args.collectionId}`; + const query = new URLSearchParams(); + if (args.access_key !== undefined) query.set('access_key', String(args.access_key)); + const url = query.toString() ? `${endpoint}?${query.toString()}` : endpoint; + const options: any = { + headers: extra.headers, + }; + const result = await extra.client.get(url, options); + + if (typeof result === 'string') { + return { + content: [ + { + type: 'text', + text: result, + }, + ], + }; + } + + const response = result as any; + if (response.collection) { + const { item, ...collectionWithoutItems } = response.collection; + + const itemRefs = buildItemRefs(item); + + const processedResponse: any = { + ...response, + collection: { + ...collectionWithoutItems, + ...(itemRefs && { itemRefs }), + }, + }; + + return { + content: [ + { + type: 'text', + text: JSON.stringify(processedResponse, null, 2), + }, + ], + }; + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; + } catch (e: unknown) { + if (e instanceof McpError) { + throw e; + } + throw asMcpError(e); + } +} diff --git a/src/tools/searchPostmanElements.ts b/src/tools/searchPostmanElements.ts new file mode 100644 index 0000000..7603daf --- /dev/null +++ b/src/tools/searchPostmanElements.ts @@ -0,0 +1,80 @@ +import { z } from 'zod'; +import { PostmanAPIClient } from '../clients/postman.js'; +import { IsomorphicHeaders, CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import { ServerContext, asMcpError, McpError } from './utils/toolHelpers.js'; + +export const method = 'searchPostmanElements'; +export const description = + 'Searches for Postman elements in the public network.\n\n**When to Use This Tool:**\n- When the user asks for a specific named request (e.g., "find PayPal requests", "search for Stripe API requests")\n- When the user explicitly wants to search the public network\n- Do NOT use this for searching the user\'s own workspaces or collections (use getCollections or getWorkspaces instead)\n\n**Search Scope:**\n- Only searches the public network (public workspaces and collections)\n- Does not search private workspaces, team workspaces, or personal collections\n- Currently supports searching for requests (entityType: "requests")\n'; +export const parameters = z.object({ + entityType: z + .literal('requests') + .describe( + 'The type of Postman [entity](https://learning.postman.com/docs/getting-started/basics/postman-elements/) (element) to search for. At this time, this only accepts the `requests` value.' + ), + q: z + .string() + .min(1) + .max(512) + .describe('The query used to search for Postman elements.') + .optional(), + publisherIsVerified: z + .boolean() + .describe( + 'Filter the search results to only return entities from publishers [verified](https://learning.postman.com/docs/collaborating-in-postman/public-api-network/verify-your-team/) by Postman.' + ) + .optional(), + nextCursor: z + .string() + .describe( + 'The cursor to get the next set of results in the paginated response. If you pass an invalid value, the API returns empty results.' + ) + .optional(), + limit: z + .number() + .int() + .gte(1) + .lte(10) + .describe('The max number of search results returned in the response.') + .default(10), +}); +export const annotations = { + title: + 'Searches for Postman elements in the public network.\n\n**When to Use This Tool:**\n- When the user asks for a specific named request (e.g., "find PayPal requests", "search for Stripe API requests")\n- When the user explicitly wants to search the public network\n- Do NOT use this for searching the user\'s own workspaces or collections (use getCollections or getWorkspaces instead)\n\n**Search Scope:**\n- Only searches the public network (public workspaces and collections)\n- Does not search private workspaces, team workspaces, or personal collections\n- Currently supports searching for requests (entityType: "requests")\n', + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, +}; + +export async function handler( + args: z.infer, + extra: { client: PostmanAPIClient; headers?: IsomorphicHeaders; serverContext?: ServerContext } +): Promise { + try { + const endpoint = `/search/${args.entityType}`; + const query = new URLSearchParams(); + if (args.q !== undefined) query.set('q', String(args.q)); + if (args.publisherIsVerified !== undefined) + query.set('publisherIsVerified', String(args.publisherIsVerified)); + if (args.nextCursor !== undefined) query.set('nextCursor', String(args.nextCursor)); + if (args.limit !== undefined) query.set('limit', String(args.limit)); + const url = query.toString() ? `${endpoint}?${query.toString()}` : endpoint; + const options: any = { + headers: extra.headers, + }; + const result = await extra.client.get(url, options); + return { + content: [ + { + type: 'text', + text: `${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}`, + }, + ], + }; + } catch (e: unknown) { + if (e instanceof McpError) { + throw e; + } + throw asMcpError(e); + } +} diff --git a/src/tools/utils/toolHelpers.ts b/src/tools/utils/toolHelpers.ts index 41843c0..8b9c3d9 100644 --- a/src/tools/utils/toolHelpers.ts +++ b/src/tools/utils/toolHelpers.ts @@ -4,7 +4,7 @@ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; export { McpError }; export interface ServerContext { - serverType: 'full' | 'minimal'; + serverType: 'full' | 'minimal' | 'code'; availableTools: string[]; }