npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@http-forge/core

v0.5.3

Published

Headless HTTP testing engine with Postman collection support, dynamic variables, and script-based automation.

Readme

@http-forge/core

Standalone HTTP testing engine with Postman collection support and JavaScript-based automation.

npm version License: MIT

📦 What is @http-forge/core?

@http-forge/core is a headless, framework-agnostic HTTP execution engine with full Postman collection compatibility. Execute complex API workflows, test suites, and automated flows without the overhead of a UI.

Core Features:

  • 🚀 Postman Collections - Load and execute .postman_collection.json and .forge.json files
  • 📝 JavaScript Scripting - Pre-request and post-response scripts with full pm.* API (variables, assertions, execution flow, visualizer)
  • 🔄 Dynamic Variables - Built-in generators: {{$randomInt}}, {{$timestamp}}, {{$uuid}}, {{$guid}}, etc.
  • 🌍 Environments - Full variable scoping (globals, collection, environment, session, iterationData)
  • Cloud Secret Resolvers - Resolve {{secret:alias/path}} from AWS Secrets Manager, Azure Key Vault, Google Secret Manager, HashiCorp Vault, 1Password, and Doppler; credentials read from the ambient environment, never from config
  • �👁️ File Watching - Automatic reload on collection/environment file changes with notification callbacks
  • 🍪 Cookie Persistence - Automatic cookie storage and reuse, pm.cookies.jar() and .toObject()
  • 📊 Test Assertions - BDD-style testing with pm.test() (sync/async), pm.test.skip() / .fail() / .index(), full Chai expect() chains (incl. .contain()), and JSON Schema validation via pm.response.to.have.jsonSchema()
  • 🔐 CryptoJS - Full crypto library: hash, HMAC, AES/DES/TripleDES, PBKDF2, encoding helpers
  • 🎯 Execution Flow - pm.setNextRequest(), pm.execution.skipRequest() for suite runner flow control
  • 📈 Visualizer - pm.visualizer.set(template, data) for custom Handlebars-based HTML output
  • 🔌 Extensible - Custom interceptors, HTTP clients, and module loaders

Ideal for:

  • CI/CD pipeline integration (GitHub Actions, GitLab CI, Jenkins)
  • Headless API testing and contract validation
  • Building custom API testing CLIs
  • Load testing and performance monitoring
  • Automated integration test suites

🎯 Installation

Requires Node.js 20+.

npm install @http-forge/core

Or using the tarball:

npm install ./http-forge-core-0.1.0.tgz

Runtime APIs (Headless + MCP)

For current programmatic runtime APIs, see RUNTIME-API.md:

  • runRequest(options)
  • runCollection(options)
  • runSuite(options)
  • createMcpRuntime(options)

This covers direct execution, embeddable MCP runtime lifecycle, response include options, and report behavior.

⚡ Quick Start

Basic Usage

import { ForgeContainer } from '@http-forge/core';

// Create a container with default settings
const forge = new ForgeContainer();

// Load a collection
const collection = await forge.loadCollection('./my-api.forge.json');

// Execute a request
const result = await forge.execute(collection.items[0], collection);

console.log(result.response.status);        // 200
console.log(result.response.body);          // Response data
console.log(result.postResponseResult?.assertions);  // Test results

With Environment Variables

const forge = new ForgeContainer();

// Set environment variables
forge.setEnvironment({
    baseUrl: 'https://api.example.com',
    apiKey: 'your-api-key',
    timeout: '5000'
});

// Variables are automatically interpolated in requests
// URL: {{baseUrl}}/users -> https://api.example.com/users
const result = await forge.execute(request, collection);

With Custom Configuration

const forge = new ForgeContainer({
    // Use native Node.js http/https instead of fetch
    useNativeHttp: true,
    
    // Enable automatic cookie management
    enableCookies: true,
    
    // Set request timeout
    requestTimeout: 10000,
    
    // Enable request history
    enableHistory: true,
    maxHistoryEntries: 50,
    
    // Storage format
    storageFormat: 'folder'  // or 'file'
});

📚 Core Concepts

ForgeContainer

The main entry point - a dependency injection container that wires up all components.

const forge = new ForgeContainer(options);

// Load collections
const collection = await forge.loadCollection(path);
const folderCollection = await forge.loadFolderCollection(path);

// Execute requests
const result = await forge.execute(request, collection, options);

// Manage environments
forge.setEnvironment(variables);
forge.setActiveEnvironment(name);
const resolved = forge.getResolvedEnvironment();

// Access services
const executor = forge.getRequestExecutor();
const loader = forge.getCollectionLoader();

Request Execution

Execute requests with full control over the execution pipeline:

const result = await forge.execute(request, collection, {
    environment: 'production',
    overrides: {
        url: 'https://override.com/api',
        headers: { 'X-Custom': 'value' }
    },
    skipPreRequest: false,
    skipPostResponse: false,
    timeout: 5000
});

// Access results
console.log(result.response);           // HTTP response
console.log(result.preRequestResult);   // Pre-request script output
console.log(result.postResponseResult); // Test results

Dynamic Variables

Automatic variable generation within request templates:

// URL with dynamic timestamp
https://api.example.com/events?timestamp={{$timestamp}}

// Headers with unique ID
X-Request-ID: {{$uuid}}

// Query parameters with random value
?page=1&seed={{$randomInt:1:100}}

Supported dynamic variables:

  • {{$randomInt}} - Random integer (0-2147483647)
  • {{$randomInt:min:max}} - Random integer in range
  • {{$timestamp}} - Current Unix timestamp (seconds)
  • {{$uuid}} - UUID v4
  • {{$guid}} - GUID (alias for uuid)
  • {{$randomString}} - 10-char alphanumeric string
  • {{$randomHexadecimal}} - Random hex string
  • {{$isoTimestamp}} - ISO 8601 timestamp

Script Execution

Run pre-request and post-response scripts with full Postman API compatibility:

Pre-request script - Set variables & modify request:

// Set variables across scopes
pm.variables.set('requestId', pm.variables.randomUUID());
pm.environment.set('token', 'abc-123');
pm.collectionVariables.set('counter', '1');

// Modify request headers
pm.request.headers.add({
    name: 'X-Request-ID',
    value: pm.variables.get('requestId')
});
pm.request.headers.update({
    name: 'Authorization',
    value: 'Bearer ' + pm.environment.get('token')
});

// Modify URL and body
pm.request.url = 'https://api.example.com' + pm.request.url;
pm.request.body.raw = JSON.stringify({ timestamp: Date.now() });

// Set cookies for next request
pm.cookies.set('sessionId', 'sess_abc123');

Post-response script - Test & extract data:

// Run assertions
pm.test('Status is 200', () => {
    pm.expect(pm.response.code).to.equal(200);
});
pm.test('Response time under 1s', () => {
    pm.expect(pm.response.responseTime).to.be.below(1000);
});

// Extract data for next request
const data = pm.response.json();
pm.environment.set('userId', data.id);
pm.environment.set('authToken', data.token);

// Store non-string values (auto-serialized with type safety)
pm.environment.set('userList', data.users);     // Array → stored with type marker
pm.environment.set('config', { retries: 3 });   // Object → stored with type marker
pm.environment.set('count', 42);                // Number → stored with type marker

// get() auto-deserializes back to the original type
const users = pm.environment.get('userList');    // → Array
const config = pm.environment.get('config');     // → Object
const count = pm.environment.get('count');       // → 42 (number)

// Strings are never misinterpreted — "true" stays a string, true stays a boolean
pm.environment.set('flag', true);               // boolean
pm.environment.set('label', 'true');            // string
pm.environment.get('flag');                     // → true (boolean)
pm.environment.get('label');                    // → "true" (string)

// Store cookies from response
if (pm.response.headers.has('Set-Cookie')) {
    pm.cookies.set('authCookie', data.authCookie);
}

Response Body Type

Like Postman, pm.response.body is the raw response string — it is not eagerly parsed. Parse JSON on demand:

const data = pm.response.json();          // parsed object (null if not JSON)
const text = pm.response.text();          // raw string
const obj  = JSON.parse(pm.response.body); // body is a string

json() tolerates an already-parsed object body for backward compatibility, but new code should treat pm.response.body as a string.

Legacy Postman Globals

Older Postman scripts and many Newman-exported collections rely on bare globals from the pre-pm.* sandbox. The core injects these so legacy scripts run unchanged.

Available in both pre-request and test scripts: request ({ id, name, description, url, method, headers, data }), environment (read snapshot), globals (read snapshot), data (iteration row), iteration (index), and tv4 (tv4.validate(data, schema) — when the optional tv4 module is installed).

Available in test scripts only: responseBody (raw string), responseCode ({ code, name, detail }), responseHeaders, responseTime, responseCookies (cookie objects with name, value, domain, path, expires, maxAge, httpOnly, secure, sameSite), tests (tests['name'] = boolean), plus postman.getResponseHeader(name) and postman.getResponseCookie(name) (case-insensitive; getResponseCookie returns the full cookie object, both return null if absent).

// Legacy-style test script — runs unmodified
tests['status 200'] = responseCode.code === 200;
tests['has token'] = JSON.parse(responseBody).token !== undefined;
tests['is json'] = /application\/json/.test(postman.getResponseHeader('Content-Type'));

environment and globals are read snapshots — direct assignment was never persisted in legacy Postman either. Use postman.setEnvironmentVariable() / postman.setGlobalVariable() (or the modern pm.environment.set() / pm.globals.set()) to persist changes.

Script Scope

Control how script levels (collection → folder → request) and the pre-request/post-response phases share a JavaScript scope via scripts.scope in http-forge.config.json (or the scriptScope SDK option):

| Mode | Behavior | |------|----------| | 'shared' (default) | All levels and both phases run in one scope. var/function and global assignments leak across them; lowest overhead. Top-level let/const are scoped to a single phase. | | 'isolated' | Each level runs in its own scope, matching Postman. Declarations cannot collide or leak; pass state through pm.variables / pm.environment / pm.globals. |

const forge = ForgeContainer.create({ scriptScope: 'isolated' });

Use 'isolated' when importing Postman collections whose scripts re-declare the same identifiers at multiple levels, which would otherwise throw Identifier already declared in shared mode.

Async Script Execution (Event-Loop Draining)

Like Postman, scripts do not end when their synchronous code returns. After the sync body completes, the core keeps the sandbox alive and drains pending timers (setTimeout/setInterval) and microtasks (un-awaited Promises), then commits variable scopes — so deferred pm.globals / pm.environment / pm.variables writes are visible to the next request without await.

// Post-response script — committed for the next request, no await required
setTimeout(() => pm.globals.set('authToken', generateToken()), 1500);

The drain is bounded by scripts.timeout (the scriptTimeout SDK option), default 5000 ms, which caps both synchronous CPU time and the async-drain window:

const forge = ForgeContainer.create({ scriptTimeout: 10000 });

| Behavior | Notes | |----------|-------| | Sandbox kept alive after sync return | Pending timers/microtasks drained before the request advances | | Deferred pm.* writes committed | Visible to the next request without await | | Errors in timer/Promise callbacks | Reported as a console error, non-fatal — the request/test is not failed | | Runaway timers | A never-clearing setInterval or infinite timer loop is force-cancelled at the timeout, with a warning | | No async work | Returns immediately; draining adds no measurable latency |

Divergences from Postman: uses real Node timers with a poll-based wall-clock drain (no exact pending-promise count, so pathological microtask recursion is bounded by the budget rather than a precise idle signal); a single scripts.timeout bounds both CPU time and the async window.

Environment Management

// Define multiple environments
forge.setEnvironmentConfig({
    dev: { baseUrl: 'https://dev.api.com', apiKey: 'dev-key' },
    staging: { baseUrl: 'https://staging.api.com', apiKey: 'staging-key' },
    prod: { baseUrl: 'https://api.com', apiKey: 'prod-key' }
});

// Switch environments
forge.setActiveEnvironment('prod');

// Get resolved variables (with inheritance and overrides)
const vars = forge.getResolvedEnvironment('prod');

🔧 Advanced Features

Custom HTTP Client

Implement your own HTTP client:

import { IHttpClient, HttpRequest, HttpResponse } from '@http-forge/core';

class CustomHttpClient implements IHttpClient {
    async send(request: HttpRequest): Promise<HttpResponse> {
        // Your custom HTTP logic
        return {
            status: 200,
            statusText: 'OK',
            headers: {},
            body: {},
            duration: 100,
            size: 1024
        };
    }
}

const forge = new ForgeContainer({
    httpClient: new CustomHttpClient()
});

Request/Response Interceptors

Add custom interceptors to modify requests and responses:

import { IRequestInterceptor, IResponseInterceptor } from '@http-forge/core';

// Request interceptor
class AuthInterceptor implements IRequestInterceptor {
    async intercept(request: HttpRequest): Promise<HttpRequest> {
        request.headers['Authorization'] = `Bearer ${getToken()}`;
        return request;
    }
}

// Response interceptor
class LoggingInterceptor implements IResponseInterceptor {
    async intercept(response: HttpResponse, request: HttpRequest): Promise<HttpResponse> {
        console.log(`${request.method} ${request.url} -> ${response.status}`);
        return response;
    }
}

const forge = new ForgeContainer({
    requestInterceptors: [new AuthInterceptor()],
    responseInterceptors: [new LoggingInterceptor()]
});

Cookie Management & Persistence

Automatic cookie storage and reuse across multi-request flows:

const forge = new ForgeContainer({
    enableCookies: true  // Cookies persist across requests in session
});

// Login - response sets Session-ID cookie
const loginResult = await forge.execute(loginRequest, collection);

// Subsequent requests automatically include Session-ID
// No need to manually extract and re-add cookies
const dataResult = await forge.execute(dataRequest, collection);
const updateResult = await forge.execute(updateRequest, collection);

Access cookies in scripts:

// Pre-request script - read stored cookies
if (pm.cookies.has('sessionId')) {
    const sid = pm.cookies.get('sessionId');
    pm.request.headers.add({
        name: 'Cookie',
        value: 'sessionId=' + sid
    });
}

// Post-response script - store new cookies
pm.response.cookies.forEach(cookie => {
    pm.cookies.set(cookie.name, cookie.value);
});

// Read a response cookie (Postman-compatible CookieList)
const sid = pm.response.cookies.get('sessionId');        // value string
const sidCookie = pm.response.cookies.one('sessionId');  // full object
if (sidCookie?.secure && sidCookie.maxAge > 0) { /* ... */ }

// List all active cookies
const allCookies = pm.cookies.list();  // [{name, value}, ...]

// Clear cookies
pm.cookies.clear();  // When switching users/sessions

Cookies are automatically extracted from Set-Cookie response headers and reused in subsequent Cookie request headers.

Request History

Track all executed requests:

const forge = new ForgeContainer({
    enableHistory: true,
    maxHistoryEntries: 100
});

// Execute requests
await forge.execute(request1, collection);
await forge.execute(request2, collection);

// Access history
const history = forge.getRequestHistory();
const entries = history.getAll();         // All requests
const byId = history.getByRequestId(id);  // Specific request history

📖 API Reference

ForgeContainer

Constructor Options:

interface ForgeContainerOptions {
    forgeRoot?: string;              // Path to http-forge folder
    storageFormat?: 'file' | 'folder'; // Collection storage format
    
    // HTTP Settings
    useNativeHttp?: boolean;         // Use native http/https
    httpClient?: IHttpClient;        // Custom HTTP client
    httpSettings?: RequestSettings;  // Default HTTP settings
    requestTimeout?: number;         // Request timeout (ms)
    
    // Cookie Settings
    enableCookies?: boolean;         // Enable cookie jar
    cookieJar?: ICookieJar;         // Custom cookie jar
    
    // Interceptors
    requestInterceptors?: IRequestInterceptor[];
    responseInterceptors?: IResponseInterceptor[];
    errorInterceptors?: IErrorInterceptor[];
    
    // Script Settings
    scriptRunner?: IScriptRunner;    // Custom script runner
    scriptTimeout?: number;          // Script budget (ms); caps CPU time + async-drain window. Default 5000
    
    // History
    enableHistory?: boolean;         // Enable request history
    maxHistoryEntries?: number;      // Max history size
    
    // File Watching
    fileWatcherFactory?: IFileWatcherFactory; // Watch for collection/environment file changes
}

Methods:

// Collection loading
loadCollection(path: string): Promise<UnifiedCollection>
loadFolderCollection(path: string): Promise<UnifiedCollection>

// Request execution
execute(
    request: UnifiedRequest,
    collection: UnifiedCollection,
    options?: ExecuteOptions
): Promise<ExecuteResult>

// Environment management
setEnvironment(variables: Record<string, string>): void
setEnvironmentConfig(config: EnvironmentConfig): void
setActiveEnvironment(name: string): void
getResolvedEnvironment(name?: string): Record<string, string>

// Service access
getRequestExecutor(): RequestExecutor
getCollectionLoader(): ICollectionLoader
getEnvironmentResolver(): EnvironmentResolver
getRequestHistory(): IRequestHistory

ExecuteResult

interface ExecuteResult {
    response: HttpResponse;          // HTTP response
    preRequestResult?: ScriptResult; // Pre-request script output
    postResponseResult?: ScriptResult; // Test results
    requestId: string;               // Unique request ID
    timestamp: number;               // Execution timestamp
}

HttpResponse

interface HttpResponse {
    status: number;                  // HTTP status code
    statusText: string;              // Status text
    headers: Record<string, string>; // Response headers
    body: any;                       // Parsed response body
    cookies?: Cookie[];              // Response cookies
    duration: number;                // Request duration (ms)
    size: number;                    // Response size (bytes)
    redirected?: boolean;            // Whether redirected
}

KeyValueEntry

Used for headers and query parameters in CollectionRequest. Supports OpenAPI metadata for generation and validation.

interface KeyValueEntry {
    key: string;
    value: string;
    enabled?: boolean;
    // OpenAPI metadata (all optional, backward-compatible)
    type?: 'string' | 'integer' | 'number' | 'boolean' | 'array';
    required?: boolean;
    description?: string;
    format?: string;               // Semantic hint (e.g. "uuid", "date-time")
    enum?: string[];               // Allowed values
    deprecated?: boolean;
    // Extended constraint fields for full OpenAPI round-trip
    pattern?: string;              // Regex validation pattern
    minimum?: number;
    maximum?: number;
    exclusiveMinimum?: number;
    exclusiveMaximum?: number;
    minLength?: number;
    maxLength?: number;
    oneOf?: Array<Record<string, any>>; // Merged constraint variants
}

PathParamEntry

Used for path parameters (:param in URLs). Same constraint fields as KeyValueEntry minus key/enabled.

interface PathParamEntry {
    value: string;
    type?: 'string' | 'integer' | 'number' | 'boolean';
    description?: string;
    format?: string;
    enum?: string[];
    deprecated?: boolean;
    pattern?: string;
    minimum?: number;
    maximum?: number;
    exclusiveMinimum?: number;
    exclusiveMaximum?: number;
    minLength?: number;
    maxLength?: number;
    oneOf?: Array<Record<string, any>>;
}

Postman Collection Import / Export

CollectionService can import and export Postman Collection v2.1 JSON files, preserving all request body types, auth, scripts, and folder structure.

Import (importCollection(filePath)):

Detects Postman format by info._postman_id and converts all five body modes:

| Postman mode | HTTP Forge type | Notes | |---|---|---| | raw | raw | format inferred from options.raw.language (json/xml/html/javascript/text) | | urlencoded | x-www-form-urlencoded | Fields mapped to [{key, value, enabled}] | | formdata | form-data | Fields mapped to [{key, value, type, enabled}] | | graphql | graphql | variables JSON string parsed to object | | binary | binary | File path not portable; imported with empty content |

Auth types bearer, basic, and apikey are converted. Pre-request and test scripts (prerequest/test events) are imported as scripts.preRequest/scripts.postResponse. Folder hierarchy and item order are preserved.

The whole collection is built in memory and persisted with a single saveCollection() call. FolderCollectionStore.save() writes sibling items and each request's independent files (body, docs, scripts, schemas) in parallel and writes each metadata file once; fresh imports skip stale-body cleanup and schema-existence probes. Loaders read JSON via a readJsonFileSafe helper that silently skips empty or half-written files, so a watcher reloading mid-import never throws Unexpected end of JSON input.

Export (exportCollection(collectionId, filePath)):

Converts back to Postman v2.1 JSON. All body types round-trip correctly: x-www-form-urlencoded exports as mode: "urlencoded", form-data as mode: "formdata", graphql as mode: "graphql", and binary as mode: "binary".

const service = container.getCollectionService();

// Import
const collection = await service.importCollection('./my-api.postman_collection.json');

// Export
await service.exportCollection(collection.id, './exported.postman_collection.json');

OpenAPI Import / Export

The core library includes full OpenAPI 3.0.3 import and export with constraint preservation.

Import (OpenApiImporter):

  • Parses OpenAPI 3.0 YAML/JSON specs into UnifiedCollection
  • Extracts all parameter schema constraints (pattern, minimum, maximum, exclusiveMinimum, exclusiveMaximum, minLength, maxLength, enum, format)
  • Preserves oneOf schemas from merged parameters, deriving combined enum hints for UI display

Export (OpenApiExporter):

  • Generates OpenAPI 3.0.3 specs from collections
  • Host-variable server resolution — Any {{varName}} that prefixes a request URL (e.g. {{dcqHost}}/api/...) is stripped from the path and resolved to a server URL entry. If the variable resolves in the active environment(s), a concrete url is added to servers. If unresolvable, an OAS server-variable entry is emitted: url: '{varName}', variables: { varName: { default: '' } }. This handles arbitrary host variables beyond {{baseUrl}}.
  • Environment variable placeholder sanitization{{varName}} tokens in parameter examples (path, query, header) and request body examples are replaced with <varName> so the exported spec contains readable human placeholders rather than raw template syntax.
  • Collision-aware merging: When multiple requests normalize to the same path + HTTP method, they are merged into a single operation:
    • Descriptions are appended, tags are unioned
    • Parameters with the same constraint kind (both enum, both pattern, etc.) are merged in-place (union enum values, widen numeric ranges, alternation-join patterns)
    • Parameters with different constraint kinds are wrapped in oneOf — each variant keeps its self-consistent schema
  • All constraint fields round-trip without data loss

� Exported Types

Configuration

HttpForgeConfig is the shape of http-forge.config.json. Key sub-interfaces:

| Interface | Description | |---|---| | StorageConfig | Root/history/results paths | | RequestConfig | Timeout, redirects, SSL | | ScriptsConfig | Module search paths | | RunnerConfig | Retention, index page size | | EnvironmentsConfig | Default environment | | RestClientExportConfig | REST Client export path/merge settings | | McpConfig | MCP server project-level settings (see below) | | ProxyConfig | HTTP/HTTPS proxy URLs |

McpConfig — controls what the HTTP Forge MCP server exposes to AI agents. All fields are optional; the service fills defaults automatically via IConfigService.getMcpConfig().

interface McpConfig {
    excludedCollections: string[];  // IDs or names to hide (default: [] = expose all)
    excludedSuites:      string[];  // IDs or names to hide (default: [] = expose all)
    toolPrefix:          string;    // Prefix for every tool name (default: "")
    maxRequestsPerCall:  number;    // Safety cap per collection/suite call (default: 500; min 1, max 10000)
    toolMode:            'flat' | 'drilldown' | 'auto'; // How tools are exposed (default: 'auto')
    drilldownThreshold:  number;    // 'auto' switches to drill-down above this request-tool count (default: 100; min 10, max 500)
    toolPageSize:        number;    // Max tools per tools/list page (default: 150; 0 = no pagination; min 10, max 1000)
    cors: {
        allowedOrigins:  string[];  // CORS origins (default: ["http://localhost","http://127.0.0.1"])
    };
}

toolMode controls how collections/requests are surfaced as MCP tools:

  • auto (default) — uses flat below drilldownThreshold (100 requests), then switches to drilldown. Adapts automatically as the workspace grows.
  • drilldown — a small fixed set of generic tools (list_collections, list_requests, run_request, run_folder, run_collection, run_suite) whose arguments select the target. Keeps the tool list tiny regardless of scale.
  • flat — one tool per request, folder, collection, and suite. Only suitable for small, stable collections (<100 requests). Avoid for large workspaces — the tool list bloats the AI context window and degrades tool-selection accuracy.

Use IConfigService.getMcpConfig() to read the resolved config with all defaults applied.


�🛠️ Use Cases

CLI Tool

#!/usr/bin/env node
import { ForgeContainer } from '@http-forge/core';

async function runTests(collectionPath: string) {
    const forge = new ForgeContainer();
    const collection = await forge.loadCollection(collectionPath);
    
    for (const request of collection.items) {
        const result = await forge.execute(request, collection);
        const tests = result.postResponseResult?.assertions || [];
        
        console.log(`\n${request.name}: ${result.response.status}`);
        tests.forEach(test => {
            console.log(`  ${test.passed ? '✓' : '✗'} ${test.name}`);
        });
    }
}

runTests(process.argv[2]);

CI/CD Integration

import { ForgeContainer } from '@http-forge/core';

async function ciTest() {
    const forge = new ForgeContainer({
        enableCookies: true,
        requestTimeout: 30000
    });
    
    forge.setEnvironment({
        baseUrl: process.env.API_URL,
        apiKey: process.env.API_KEY
    });
    
    const collection = await forge.loadCollection('./api-tests.forge.json');
    
    let failedTests = 0;
    for (const request of collection.items) {
        const result = await forge.execute(request, collection);
        const failed = result.postResponseResult?.assertions?.filter(t => !t.passed) || [];
        failedTests += failed.length;
    }
    
    process.exit(failedTests > 0 ? 1 : 0);
}

Custom Testing Framework

import { ForgeContainer } from '@http-forge/core';

class ApiTestRunner {
    private forge: ForgeContainer;
    
    constructor() {
        this.forge = new ForgeContainer({
            enableCookies: true,
            enableHistory: true
        });
    }
    
    async runSuite(suites: TestSuite[]) {
        for (const suite of suites) {
            await this.runTests(suite);
        }
    }
    
    async runTests(suite: TestSuite) {
        const collection = await this.forge.loadCollection(suite.collection);
        
        for (const request of collection.items) {
            const result = await this.forge.execute(request, collection);
            suite.results.push(result);
        }
    }
}

Multi-Request Workflows

Execute dependent request chains with automatic cookie and variable management:

import { ForgeContainer } from '@http-forge/core';

async function apiAuthWorkflow() {
    const forge = new ForgeContainer({
        enableCookies: true,  // Cookies persist across requests
        enableHistory: true
    });

    // Request 1: Login (sets session cookie)
    const loginReq = {
        name: 'Login',
        method: 'POST',
        url: 'https://api.example.com/auth/login',
        body: { type: 'raw', content: JSON.stringify({ 
            username: '{{email}}',
            password: '{{password}}'
        })},
        scripts: {
            postResponse: `
                const { token, userId } = pm.response.json();
                pm.environment.set('authToken', token);
                pm.environment.set('userId', userId);
            `
        }
    };
    
    const loginResult = await forge.execute(loginReq, collection);
    console.log('✓ Logged in, token:', forge.getResolvedEnvironment()['authToken']);
    
    // Request 2: Fetch user profile (uses session cookie automatically)
    const profileReq = {
        name: 'Get Profile',
        method: 'GET',
        url: 'https://api.example.com/users/{{userId}}',
        headers: {
            'Authorization': 'Bearer {{authToken}}'  // Uses token from login
        }
    };
    
    const profileResult = await forge.execute(profileReq, collection);
    console.log('✓ Profile:', profileResult.response.body);
    
    // Request 3: Update profile (session cookie still active)
    const updateReq = {
        name: 'Update Profile',
        method: 'PATCH',
        url: 'https://api.example.com/users/{{userId}}',
        headers: {
            'Authorization': 'Bearer {{authToken}}'
        },
        body: { type: 'raw', content: JSON.stringify({ status: 'active' })}
    };
    
    const updateResult = await forge.execute(updateReq, collection);
    console.log('✓ Updated profile');
    
    // Session automatically logged out - cookies cleared
    forge.getRequestHistory().clear();
}

📦 Storage Formats

File Format (Single JSON)

my-api.forge.json

Folder Format (Directory Structure)

my-api/
  collection.json
  requests/
    login/
      request.json
      doc.md               # Optional request documentation (Markdown)
    users/
      get-users/
        request.json
        doc.md
      create-user/
        request.json

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

MIT © Henry Huang

🔗 Links

📝 Changelog

0.2.16 (Duplicate Collection & Folder)

  • duplicateCollection(sourceId, newName) — Duplicates a collection including all nested folders, requests, scripts, and schemas
  • duplicateFolder(collectionId, folderId, newName) — Duplicates a folder within a collection, preserving all nested request content
  • getItemPath(collectionId, itemId) — Resolves the disk path of a folder or request within a collection

0.2.6 (OpenAPI Export — Environment Variable Handling)

  • Host-variable URL stripping — Any leading {{varName}} in a request URL (e.g. {{dcqHost}}/DCQ/templates/GetEpg) is now correctly stripped from the path. The variable name is resolved via envConfigService.resolveVariables() and the resulting URL is added to servers. When the variable cannot be resolved, an OAS server-variable entry (url: '{varName}', variables: { varName: { default: '' } }) is emitted instead. Previously, unknown host variables were converted to /{varName}/... path segments.
  • Example sanitization{{varName}} placeholders in parameter examples (path, query, header) and request body examples are replaced with <varName> so they render as readable human placeholders instead of raw template syntax in the exported spec.

0.2.5 (OpenAPI Constraint Round-Trip & Collision Merging)

  • Full parameter constraint round-trip — OpenAPI import/export now preserves all schema constraint fields: pattern, minimum, maximum, exclusiveMinimum, exclusiveMaximum, minLength, maxLength, and oneOf on both KeyValueEntry and PathParamEntry
  • Collision-aware OpenAPI export — When multiple requests normalize to the same path + HTTP method, the exporter merges them into a single operation with intelligent parameter schema merging (same-kind merge in-place, different-kind wraps in oneOf)
  • oneOf import with UI hintsapplyOneOfHints() derives combined enum arrays and type hints from oneOf variants for downstream UI rendering (combobox vs strict dropdown)
  • format vs pattern separationformat is a semantic hint (e.g. "uuid"), pattern is an enforced regex. Both stored and round-tripped independently

0.2.4 (File Watching & Request Documentation)

  • File watching for CollectionService - Automatic reload and onCollectionsChanged callback when collection files change on disk
  • File watching for EnvironmentConfigService - Automatic reload and onEnvironmentsChanged callback when environment JSON files change on disk
  • Request documentation (doc.md) - Persist per-request Markdown documentation as doc.md alongside request.json
  • doc field on UnifiedRequest / CollectionRequest - Optional doc?: string field for request documentation content
  • fileWatcherFactory bootstrap option - Pass IFileWatcherFactory to enable file watching in both collection and environment services

0.2.0 (Postman API Parity)

  • Async pm.test() support - Tests with async callbacks are properly awaited
  • Expect chain assertions - .a(type), .an(type), .deep.equal(), .lengthOf(), .exist, .members(), .keys(), .string()
  • pm.iterationData scope - Data-driven testing with iteration variables
  • Response status getters - response.to.be.ok, .error, .clientError, .serverError work as getters and functions
  • Response headers HeaderList - .get(), .has(), .toObject(), .each() on pm.response.headers
  • pm.cookies.toObject() - Flat {name: value} cookie map
  • replaceIn() on all scopes - Available on pm.variables, pm.environment, pm.collectionVariables, pm.globals
  • Sandbox globals - xml2Json(), jsonStringify(), jsonParse() available in scripts
  • Request headers API - .toObject(), .each() on pm.request.headers
  • pm.execution.setNextRequest() - Runner flow control (jump to named request or stop with null)
  • pm.execution.skipRequest() - Skip HTTP call from pre-request scripts
  • pm.setNextRequest() - Top-level alias for flow control
  • pm.visualizer.set(template, data) - Custom HTML visualization with Handlebars templates
  • pm.request.url as Url object - Postman SDK-compatible Url with getHost(), getPath(), getQueryString(), protocol, host, port, path, query, hash
  • Full CryptoJS - AES/DES/TripleDES encrypt/decrypt, PBKDF2, all hash algorithms (SHA1/256/384/512/SHA3/MD5/RIPEMD160), HMAC, encoding helpers (Hex/Base64/Utf8/Latin1/Utf16)

0.1.0 (Initial Release)

  • Core request execution with Postman collection support
  • Dynamic variables - 7 generators for on-the-fly value generation
  • Postman-compatible scripting - pm.* API with full feature parity
  • Variable scoping - globals, collection, environment, session, flow-level
  • Cookie persistence - automatic storage and reuse across request chains
  • Pre-request & post-response scripts with shared VM context
  • Test assertions with BDD-style pm.test() and expect chains
  • Environment management with variable substitution
  • Cookie jar with domain-aware matching
  • Request/response interceptors for customization
  • Request history tracking
  • File and folder collection formats (Postman-compatible)
  • Full TypeScript support with comprehensive types
  • Dependency injection for extensibility
  • Comprehensive test coverage