{"id":48360676,"url":"https://github.com/dever-labs/mockly","last_synced_at":"2026-05-22T01:27:45.673Z","repository":{"id":349321046,"uuid":"1201556924","full_name":"dever-labs/mockly","owner":"dever-labs","description":"Cross-platform multi-protocol mock server — HTTP, WebSocket, gRPC, GraphQL, TCP, Redis, SMTP, MQTT — with a web UI, scenario system,  and fault injection.","archived":false,"fork":false,"pushed_at":"2026-05-17T10:46:25.000Z","size":2552,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-17T11:44:07.898Z","etag":null,"topics":["ci","cross-platform","developer-tools","fault-injection","go","grpc","http","integration-testing","mock-server","mqtt","redis","smtp","testing","websocket"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dever-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-04T20:51:56.000Z","updated_at":"2026-05-17T10:46:27.000Z","dependencies_parsed_at":"2026-04-25T14:04:51.470Z","dependency_job_id":null,"html_url":"https://github.com/dever-labs/mockly","commit_stats":null,"previous_names":["dever-labs/mockly"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/dever-labs/mockly","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dever-labs%2Fmockly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dever-labs%2Fmockly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dever-labs%2Fmockly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dever-labs%2Fmockly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dever-labs","download_url":"https://codeload.github.com/dever-labs/mockly/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dever-labs%2Fmockly/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33320451,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T12:23:38.849Z","status":"ssl_error","status_checked_at":"2026-05-21T12:22:11.673Z","response_time":62,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ci","cross-platform","developer-tools","fault-injection","go","grpc","http","integration-testing","mock-server","mqtt","redis","smtp","testing","websocket"],"created_at":"2026-04-05T12:06:52.438Z","updated_at":"2026-05-22T01:27:45.667Z","avatar_url":"https://github.com/dever-labs.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mockly\n\n**Cross-platform, multi-protocol mock server** — HTTP, WebSocket, gRPC, GraphQL, TCP, Redis, SMTP, MQTT, SNMP, DNS, AMQP, Kafka, LDAP, IMAP, FTP, Memcached, STOMP, CoAP, and SIP in a single binary with a built-in web UI, REST management API, scenario system, and fault injection.\n\n[![CI](https://github.com/dever-labs/mockly/actions/workflows/ci.yml/badge.svg)](https://github.com/dever-labs/mockly/actions/workflows/ci.yml)\n[![Latest release](https://img.shields.io/github/v/release/dever-labs/mockly)](https://github.com/dever-labs/mockly/releases/latest)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n---\n\n## Table of contents\n\n- [Features](#features)\n- [Quickstart](#quickstart)\n- [Configuration](#configuration)\n- [Protocols](#protocols)\n- [Component Testing](#component-testing)\n- [Scenarios](#scenarios)\n- [Fault Injection](#fault-injection)\n- [PATCH Mocks](#patch-mocks)\n- [Preset Configs](#preset-configs)\n- [CLI Reference](#cli-reference)\n- [Management API Reference](#management-api-reference)\n- [Client Libraries](#client-libraries)\n- [CI Integration](#ci-integration)\n- [Architecture](#architecture)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Features\n\n| Feature | Details |\n|---|---|\n| **Protocols** | HTTP, WebSocket, gRPC, GraphQL, TCP, Redis, SMTP, MQTT, SNMP, DNS, AMQP, Kafka, LDAP, IMAP, FTP, Memcached, STOMP, CoAP, SIP |\n| **Request matching** | Method + path (exact / wildcard / regex), headers, query params, JSON body fields |\n| **Response sequences** | Return a different response on each successive call — loop, hold last, or 404 when exhausted |\n| **Response control** | Status code, headers, body, artificial delay |\n| **Template responses** | Go template syntax in response bodies and headers (`{{now}}`, `{{.query.foo}}`, `{{.body}}`, etc.) |\n| **State conditions** | Fire a mock only when a runtime state variable matches |\n| **Scenarios** | Named sets of mock patches — activate/deactivate atomically via API or CLI |\n| **Per-protocol fault injection** | Each protocol exposes its own native fault fields (DNS rcode, gRPC status code, Kafka error code, etc.) — activate via API or bundled inside a scenario |\n| **Per-mock fault injection** | Fault fields on individual HTTP mocks with independent delay, status/body override, and error rate |\n| **Call verification** | Track how many times each mock was hit; block until an expected count is reached |\n| **PATCH mocks** | Change only specific response fields at runtime without replacing the whole mock |\n| **Preset configs** | Drop-in YAML configs for Keycloak, Authelia, OAuth2, GitHub, Stripe, OpenAI, Slack, Twilio, SendGrid |\n| **Web UI** | Served from the binary itself — no separate install |\n| **Management API** | 40+ REST endpoints covering all protocols, scenarios, fault, state, logs, and call counts |\n| **Live request log** | SSE-streamed in real time to the UI |\n| **CI-friendly** | Zero dependencies, single binary, YAML config, Docker image |\n\n---\n\n## Quickstart\n\n### Download\n\nGrab the binary for your platform from the [releases page](https://github.com/dever-labs/mockly/releases), or build from source:\n\n```sh\ngit clone https://github.com/dever-labs/mockly\ncd mockly\nmake build        # builds UI + Go binary\n```\n\n### Run with a config file\n\n```sh\nmockly start --config mockly.yaml\n```\n\nOpen `http://localhost:9091` for the web UI, or call the management API at the same port.\n\n### Run a preset\n\n```sh\nmockly preset use keycloak    # starts Mockly pre-loaded with Keycloak endpoints\nmockly preset list            # list all available presets\nmockly preset show stripe     # print the preset YAML\n```\n\n---\n\n## Configuration\n\nMockly is driven by a YAML config file. Every section is optional.\n\n```yaml\nmockly:\n  api:\n    port: 9091   # Management API + Web UI port (default: 9091)\n    # cors:      # CORS for the management API. Defaults to wide-open (\"*\").\n    #   enabled: true                          # Set false to disable CORS headers entirely\n    #   allowed_origins: [\"http://localhost:3000\"]\n    #   allowed_methods: [\"GET\",\"POST\",\"PUT\",\"DELETE\",\"OPTIONS\"]\n    #   allowed_headers: [\"Content-Type\",\"Authorization\"]\n\nprotocols:\n  http:\n    enabled: true\n    port: 8080\n    # max_body_bytes: 10485760  # Request body size limit in bytes (0 = unlimited, default)\n    mocks:\n      - id: list-users\n        request:\n          method: GET\n          path: /api/users\n        response:\n          status: 200\n          headers:\n            Content-Type: application/json\n          body: '[{\"id\":1,\"name\":\"Alice\"}]'\n          delay: 50ms\n\n  websocket:\n    enabled: true\n    port: 8081\n    mocks:\n      - id: echo\n        path: /ws/echo\n        on_message:\n          - match: ping\n            respond: pong\n\n  grpc:\n    enabled: true\n    port: 50051\n    services:\n      - proto: ./protos/users.proto\n        mocks:\n          - id: get-user\n            method: GetUser\n            response:\n              id: \"1\"\n              name: Alice\n\n  graphql:\n    enabled: true\n    port: 8082\n    path: /graphql\n    mocks:\n      - id: get-user\n        operation_type: query\n        operation_name: GetUser\n        response:\n          user:\n            id: \"1\"\n            name: Alice\n\n  tcp:\n    enabled: true\n    port: 8083\n    mocks:\n      - id: hello\n        match: \"HELLO\"\n        response: \"WORLD\\n\"\n\n  redis:\n    enabled: true\n    port: 6379\n    mocks:\n      - id: get-session\n        command: GET\n        key: \"session:*\"\n        response:\n          type: bulk\n          value: '{\"userId\":\"abc\"}'\n\n  smtp:\n    enabled: true\n    port: 2525\n    domain: mockly.local\n    rules:\n      - id: accept-all\n        action: accept\n\n  mqtt:\n    enabled: true\n    port: 1883\n    mocks:\n      - id: sensor-ack\n        topic: \"sensors/+\"\n        response:\n          topic: \"sensors/ack\"\n          payload: '{\"ok\":true}'\n\nscenarios:\n  - id: auth-down\n    name: Auth Service Down\n    description: Simulate auth outage — all token endpoints return 503\n    patches:\n      - mock_id: list-users\n        status: 503\n        body: '{\"error\":\"auth unavailable\"}'\n```\n\n### Path matching\n\n| Pattern | Matches |\n|---|---|\n| `/api/users` | Exact match |\n| `/api/*` | Any path starting with `/api/` |\n| `re:^/users/\\d+$` | Regex — any `/users/\u003cnumber\u003e` |\n\n### Template responses\n\nResponse bodies **and response headers** are rendered as Go templates. Built-in functions:\n\n| Function | Example | Description |\n|---|---|---|\n| `{{now}}` | `2024-01-15T10:30:00Z` | Current UTC time (RFC3339) |\n| `{{date \"2006-01-02\"}}` | `2024-01-15` | Current date in Go format |\n| `{{date_add \"2006-01-02\" \"-7d\"}}` | `2024-01-08` | Date with duration offset |\n| `{{uuid}}` | `550e8400-e29b-41d4-a716-446655440000` | Random UUID v4 |\n| `{{rand_int 1 100}}` | `42` | Random integer in [min, max] |\n| `{{rand_float 0.0 1.0 2}}` | `0.73` | Random float with N decimal places |\n| `{{rand_string 8}}` | `aB3xKp7m` | Random alphanumeric string |\n| `{{rand_string 8 \"hex\"}}` | `3f9a1c2b` | Charset: `alpha`, `lower`, `upper`, `numeric`, `hex`, `alphanumeric`, or custom |\n| `{{rand_bool}}` | `true` | Random boolean |\n| `{{pick \"a\" \"b\" \"c\"}}` | `b` | Randomly pick one of the given values |\n| `{{fake \"name\"}}` | `Alice Smith` | Fake full name |\n| `{{fake \"email\"}}` | `alice.smith@example.com` | Fake email |\n| `{{fake \"phone\"}}` | `+1-555-0142` | Fake phone number |\n| `{{fake \"company\"}}` | `Apex Labs` | Fake company name |\n| `{{fake \"city\"}}` | `Berlin` | Fake city |\n| `{{fake \"country\"}}` | `Germany` | Fake country |\n| `{{fake \"street\"}}` | `42 Main St` | Fake street address |\n| `{{fake \"zip\"}}` | `10115` | Fake postal code |\n| `{{fake \"ip\"}}` | `192.168.1.42` | Fake IPv4 |\n| `{{fake \"ipv6\"}}` | `2001:db8::1a2b:3c4d` | Fake IPv6 |\n| `{{fake \"url\"}}` | `https://apex.io/api/lorem` | Fake URL |\n| `{{fake \"username\"}}` | `alice42` | Fake username |\n| `{{fake \"useragent\"}}` | `Mozilla/5.0 …` | Random User-Agent string |\n| `{{fake \"word\"}}` | `lorem` | Single lorem ipsum word |\n| `{{fake \"sentence\"}}` | `lorem ipsum dolor sit amet` | Short lorem ipsum phrase |\n| `{{seq \"counter\"}}` | `1`, `2`, `3`, … | Auto-incrementing integer per named counter |\n| `{{lorem 5}}` | `lorem ipsum dolor sit amet` | N lorem ipsum words |\n| `{{upper \"hello\"}}` | `HELLO` | Uppercase string |\n| `{{lower \"WORLD\"}}` | `world` | Lowercase string |\n| `{{.body}}` | *(request body)* | Incoming request body |\n| `{{.headers.X-Foo}}` | *(header value)* | Incoming request header |\n| `{{.query.foo}}` | *(query value)* | Incoming request query parameter |\n| `{{state \"key\"}}` | *(state value)* | Value from runtime state store |\n\n**Sequence counters** (`{{seq \"name\"}}`) are reset to zero by `POST /api/reset` or `mockly reset`.\n\nExample — generate a realistic user object on every request:\n\n```yaml\nresponse:\n  status: 200\n  headers:\n    Content-Type: application/json\n  body: |\n    {\n      \"id\": \"{{uuid}}\",\n      \"name\": \"{{fake \"name\"}}\",\n      \"email\": \"{{fake \"email\"}}\",\n      \"role\": \"{{pick \"user\" \"admin\" \"viewer\"}}\",\n      \"score\": {{rand_float 0 100 1}},\n      \"created_at\": \"{{now}}\"\n    }\n```\n\n---\n\n## Protocols\n\n### HTTP\n\nFull HTTP mock server. Matching on method + path (exact/wildcard/regex), optional query params, header match, JSON body field match, and state condition.\n\n```yaml\nprotocols:\n  http:\n    enabled: true\n    port: 8080\n    max_body_bytes: 10485760  # optional: limit request body size (bytes); 0 = unlimited (default)\n    mocks:\n      - id: create-user\n        request:\n          method: POST\n          path: /users\n          headers:\n            Authorization: \"Bearer *\"\n        response:\n          status: 201\n          body: '{\"id\":\"{{uuid}}\"}'\n          headers:\n            Content-Type: application/json\n          delay: 20ms\n```\n\n#### Query parameter matching\n\n```yaml\n      - id: admin-users\n        request:\n          method: GET\n          path: /users\n          query:\n            role: admin          # exact match\n            page: \"*\"            # any value (wildcard)\n        response:\n          status: 200\n          body: '[{\"id\":1,\"role\":\"admin\"}]'\n```\n\n#### JSON body field matching\n\nUse dot-notation paths to match fields anywhere in a JSON body:\n\n```yaml\n      - id: gbp-payment\n        request:\n          method: POST\n          path: /payments\n          body_json:\n            currency: GBP        # exact\n            \"user.tier\": premium # nested: {\"user\":{\"tier\":\"premium\"}}\n            \"items.0.sku\": \"*\"   # any SKU (wildcard)\n        response:\n          status: 200\n          body: '{\"ok\":true}'\n```\n\n#### Response sequences\n\nReturn a different response on each successive call. Useful for simulating transient errors or pagination.\n\n```yaml\n      - id: flaky-service\n        request:\n          method: GET\n          path: /data\n        sequence:\n          - status: 503\n            body: '{\"error\":\"unavailable\"}'\n          - status: 503\n            body: '{\"error\":\"unavailable\"}'\n          - status: 200\n            body: '{\"data\":\"ok\"}'\n        sequence_exhausted: hold_last   # hold_last (default) | loop | not_found\n        response:\n          status: 200\n          body: '{\"data\":\"ok\"}'\n```\n\n| `sequence_exhausted` | Behaviour after all entries are consumed |\n|---|---|\n| `hold_last` | Keep returning the last entry (default) |\n| `loop` | Restart from the first entry |\n| `not_found` | Return 404 |\n\n#### Per-mock fault injection\n\nEvery HTTP mock can have its own `fault:` block — independently of protocol-level faults. This is useful for targeted latency tests or intermittent failures on one endpoint without affecting the rest of the protocol server.\n\n```yaml\n      - id: slow-search\n        request:\n          method: GET\n          path: /search\n        fault:\n          delay: 2s       # add latency\n          error_rate: 0.5 # only apply 50% of the time (0 = always)\n        response:\n          status: 200\n          body: '[]'\n```\n\n### WebSocket\n\n```yaml\nprotocols:\n  websocket:\n    enabled: true\n    port: 8081\n    mocks:\n      - id: ticker\n        path: /ws/ticker\n        on_connect:\n          send: '{\"type\":\"connected\"}'\n        on_message:\n          match: subscribe\n          respond: '{\"type\":\"tick\",\"price\":42.0}'\n```\n\n### gRPC\n\nDynamic gRPC mocking — no compiled `.proto` files needed. Uses a raw codec to intercept any service/method call.\n\n```yaml\nprotocols:\n  grpc:\n    enabled: true\n    port: 50051\n    services:\n      - proto: ./protos/payments.proto   # informational — no compilation needed\n        mocks:\n          - id: charge\n            method: Charge\n            response:\n              success: true\n              charge_id: ch_123\n```\n\n### GraphQL\n\nHTTP-based GraphQL mock. Handles `POST /graphql` with `application/json` and `application/graphql` content types, plus `GET` requests with a `query` parameter. Introspection queries return an empty schema.\n\n```yaml\nprotocols:\n  graphql:\n    enabled: true\n    port: 8082\n    path: /graphql\n    mocks:\n      - id: create-post\n        operation_type: mutation\n        operation_name: CreatePost\n        response:\n          createPost:\n            id: \"{{uuid}}\"\n            title: Hello\n        errors: []\n```\n\n### TCP\n\nRaw TCP mock server. Matches incoming data as exact string, prefix wildcard, or regex. Supports hex encoding for binary protocols.\n\n```yaml\nprotocols:\n  tcp:\n    enabled: true\n    port: 8083\n    mocks:\n      - id: ping\n        match: \"PING\\r\\n\"\n        response: \"+PONG\\r\\n\"\n      - id: hex-response\n        match: \"re:^\\\\x02.*\\\\x03$\"\n        response: \"hex:060000\"\n```\n\n### Redis\n\nRESP-protocol Redis mock. Intercepts any Redis command and returns a configurable response.\n\n```yaml\nprotocols:\n  redis:\n    enabled: true\n    port: 6379\n    mocks:\n      - id: auth\n        command: AUTH\n        response:\n          type: string     # string | bulk | integer | null | error | array\n          value: \"OK\"\n      - id: get-token\n        command: GET\n        key: \"token:*\"\n        response:\n          type: bulk\n          value: \"abc123\"\n          delay: 5ms\n```\n\n### SMTP\n\nSMTP server that captures emails and applies accept/reject rules.\n\n```yaml\nprotocols:\n  smtp:\n    enabled: true\n    port: 2525\n    domain: mockly.local\n    rules:\n      - id: reject-spam\n        from: \"*@spam.example.com\"\n        action: reject\n        message: \"550 spam not accepted\"\n      - id: accept-all\n        action: accept\n```\n\nCaptured emails are visible at `GET /api/emails`.\n\n### MQTT\n\nFull MQTT v3/v4/v5 broker (powered by mochi-mqtt). Configurable topic pattern matching with automatic response publishing.\n\n```yaml\nprotocols:\n  mqtt:\n    enabled: true\n    port: 1883\n    mocks:\n      - id: command-ack\n        topic: \"devices/+/command\"\n        response:\n          topic: \"devices/+/ack\"\n          payload: '{\"status\":\"ok\"}'\n          qos: 1\n```\n\nTopic wildcards: `+` matches a single segment, `#` matches everything below.\n\n---\n\n### SNMP\n\nFull SNMP agent (powered by GoSNMPServer) that responds to GET, GETNEXT, GETBULK, and SET requests. Supports SNMPv1, v2c, and v3 (USM with MD5/SHA auth and DES/AES privacy). Can also send outbound TRAPs to any target host via the management API.\n\n```yaml\nprotocols:\n  snmp:\n    enabled: true\n    port: 1161            # default; 161 requires root / CAP_NET_BIND_SERVICE\n    community: \"public\"   # v1/v2c community string\n    v3_users:\n      - username: mocklyuser\n        auth_protocol: md5        # md5 | sha | sha224 | sha256 | sha384 | sha512\n        auth_passphrase: mocklyauth\n        priv_protocol: des        # des | aes | aes192 | aes256\n        priv_passphrase: mocklypriv\n    mocks:\n      - id: sys-descr\n        oid: 1.3.6.1.2.1.1.1.0\n        type: string\n        value: \"Mockly Virtual Device\"\n      - id: sys-uptime\n        oid: 1.3.6.1.2.1.1.3.0\n        type: timeticks\n        value: 987654\n      - id: if-number\n        oid: 1.3.6.1.2.1.2.1.0\n        type: integer\n        value: 4\n    traps:\n      - id: cold-start\n        target: \"127.0.0.1:1162\"\n        version: \"2c\"\n        community: \"public\"\n        oid: 1.3.6.1.6.3.1.1.5.1\n        bindings:\n          - oid: 1.3.6.1.2.1.1.1.0\n            type: string\n            value: \"Device restarted\"\n```\n\n**Supported OID types:**\n\n| `type` value | SNMP ASN.1 type | Example `value` |\n|---|---|---|\n| `string` / `octetstring` | OctetString | `\"Mockly Virtual Device\"` |\n| `integer` / `int` | Integer | `42` |\n| `gauge32` | Gauge32 | `100` |\n| `counter32` | Counter32 | `1048576` |\n| `counter64` | Counter64 | `9000000000` |\n| `timeticks` | TimeTicks | `987654` |\n| `ipaddress` | IPAddress | `\"192.168.1.1\"` |\n| `objectidentifier` / `oid` | ObjectIdentifier | `\"1.3.6.1.2.1.1.2.0\"` |\n\n**TRAP sending** — POST to `/api/snmp/traps/{id}/send` to trigger any configured trap. The agent connects to the trap's `target` over UDP and sends the PDU.\n\n### DNS\n\nFull DNS mock server over UDP and TCP. Responds to `A`, `AAAA`, `CNAME`, `MX`, `TXT`, `PTR`, `SRV`, and `NS` queries. `records` hold the raw answer values; for `MX` use `\"\u003cpriority\u003e \u003chost\u003e\"`, and for `SRV` use `\"\u003cpriority\u003e \u003cweight\u003e \u003cport\u003e \u003ctarget\u003e\"`.\n\n```yaml\nprotocols:\n  dns:\n    enabled: true\n    port: 5353\n    mocks:\n      - id: api-host\n        name: \"api.example.com\"\n        type: A\n        records:\n          - \"127.0.0.1\"\n        ttl: 60\n      - id: mail\n        name: \"example.com\"\n        type: MX\n        records:\n          - \"10 mail.example.com\"\n```\n\n### AMQP\n\nAMQP 0.9.1 mock broker. Handles connection, channel, and basic frames, supports publish/consume flows, and captures published messages for inspection via the management API.\n\n```yaml\nprotocols:\n  amqp:\n    enabled: true\n    port: 5672\n    mocks:\n      - id: order-created\n        exchange: orders\n        routing_key: \"order.created\"\n        response:\n          body: '{\"status\":\"accepted\"}'\n```\n\n### Kafka\n\nKafka wire-protocol mock covering ApiVersions, Metadata, Produce, and Fetch flows. Published messages are stored for later inspection.\n\n```yaml\nprotocols:\n  kafka:\n    enabled: true\n    port: 9092\n    mocks:\n      - id: orders-topic\n        topic: orders\n        records:\n          - key: \"order-1\"\n            value: '{\"id\":1,\"status\":\"pending\"}'\n```\n\n### LDAP\n\nLDAP mock server handling Bind (success) and Search requests. Matches on base DN and filter, then returns configured attributes.\n\n```yaml\nprotocols:\n  ldap:\n    enabled: true\n    port: 3893\n    mocks:\n      - id: user-lookup\n        base_dn: \"dc=example,dc=com\"\n        filter: \"*\"\n        attributes:\n          cn:\n            - \"Alice Smith\"\n          mail:\n            - \"alice@example.com\"\n          uid:\n            - \"alice\"\n```\n\n### IMAP\n\nIMAP4rev1 mock server serving pre-configured mailboxes and messages. Supports LOGIN, SELECT, FETCH, SEARCH, and LOGOUT.\n\n```yaml\nprotocols:\n  imap:\n    enabled: true\n    port: 1143\n    mailboxes:\n      - id: inbox\n        name: INBOX\n        messages:\n          - seq_num: 1\n            from: \"sender@example.com\"\n            to: \"user@example.com\"\n            subject: \"Test email\"\n            body: \"Hello world\"\n```\n\n### FTP\n\nFTP mock server with PASV support plus LIST, RETR, STOR, and DELE. Files are pre-loaded from config.\n\n```yaml\nprotocols:\n  ftp:\n    enabled: true\n    port: 2121\n    files:\n      - id: daily-report\n        path: /reports/daily.csv\n        content: |\n          date,revenue\n          2024-01-01,1000\n      - id: app-config\n        path: /data/config.json\n        content: '{\"version\":\"1.0\"}'\n```\n\n### Memcached\n\nMemcached text-protocol mock handling `get`, `set`, `delete`, `flush_all`, `stats`, and `quit`. Keys support `*` wildcards and `re:` regex patterns.\n\n```yaml\nprotocols:\n  memcached:\n    enabled: true\n    port: 11211\n    mocks:\n      - id: session-cache\n        command: get\n        key: \"session:*\"\n        response:\n          value: '{\"user_id\":42,\"role\":\"admin\"}'\n      - id: any-delete\n        command: delete\n        key: \"*\"\n        response:\n          status: DELETED\n```\n\n### STOMP\n\nSTOMP 1.2 broker mock handling CONNECT, SEND, SUBSCRIBE, UNSUBSCRIBE, and DISCONNECT. Matching destinations can publish configured MESSAGE frames and captured inbound messages are stored for inspection.\n\n```yaml\nprotocols:\n  stomp:\n    enabled: true\n    port: 61613\n    mocks:\n      - id: process-order\n        destination: \"/queue/orders\"\n        response:\n          body: '{\"status\":\"queued\"}'\n          content_type: application/json\n```\n\n### CoAP\n\nCoAP UDP mock server handling GET, POST, PUT, and DELETE requests. Matches on method + path with exact, wildcard, or regex patterns.\n\n```yaml\nprotocols:\n  coap:\n    enabled: true\n    port: 5683\n    mocks:\n      - id: temperature\n        method: GET\n        path: /sensors/temperature\n        response:\n          code: \"2.05\"\n          payload: \"23.5\"\n          content_format: 0   # text/plain\n```\n\n### SIP\n\nSIP UDP mock server handling INVITE, REGISTER, OPTIONS, BYE, CANCEL, and ACK. Matches on method + URI using exact, wildcard, or regex patterns.\n\n```yaml\nprotocols:\n  sip:\n    enabled: true\n    port: 5060\n    mocks:\n      - id: invite-ok\n        method: INVITE\n        uri: \"sip:*@example.com\"\n        response:\n          status: 200\n          reason: \"OK\"\n      - id: register\n        method: REGISTER\n        uri: \"*\"\n        response:\n          status: 200\n          reason: \"OK\"\n```\n\n---\n\n## Component Testing\n\nMockly is designed for **component testing** — testing how your application behaves when a dependency returns errors, timeouts, unexpected data, or edge-case responses. The config file is owned by the dependency team; consuming teams just load it and toggle scenarios.\n\n### Typical workflow\n\n```\ndependency-service/\n└── mockly/\n    ├── mockly.yaml          # base happy-path mocks\n    └── scenarios/           # optional split-out scenario files\n        ├── auth-down.yaml\n        └── payment-timeout.yaml\n```\n\nYour test:\n\n```go\n// start mockly with the dependency's config\n// activate a scenario to simulate a failure\n// make requests to your app and assert it handles the error correctly\n// call the verification API to confirm your app called the right endpoints\n```\n\n### Call verification\n\nCheck how many times your app hit a mock — without log scraping:\n\n```sh\n# How many times was POST /token called?\ncurl http://localhost:9091/api/calls/http/token-endpoint\n\n# Block until the mock has been called at least 3 times (timeout 5s)\ncurl -X POST http://localhost:9091/api/calls/http/token-endpoint/wait \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"count\":3,\"timeout\":\"5s\"}'\n\n# Reset call counters for one mock\ncurl -X DELETE http://localhost:9091/api/calls/http/token-endpoint\n\n# Reset all call counters\ncurl -X DELETE http://localhost:9091/api/calls/http\n```\n\nResponse from `GET /api/calls/http/{mockId}`:\n\n```json\n{\n  \"mock_id\": \"token-endpoint\",\n  \"count\": 3,\n  \"calls\": [\n    {\"id\": \"...\", \"mock_id\": \"token-endpoint\", \"method\": \"POST\", \"path\": \"/token\", \"timestamp\": \"...\"}\n  ]\n}\n```\n\n### Full component-test example\n\n```yaml\nprotocols:\n  http:\n    enabled: true\n    port: 8080\n    mocks:\n      # Happy path\n      - id: get-token\n        request:\n          method: POST\n          path: /oauth/token\n          body_json:\n            grant_type: client_credentials\n        response:\n          status: 200\n          body: '{\"access_token\":\"{{uuid}}\",\"expires_in\":3600}'\n\n      # Sequence: simulate token refresh after expiry\n      - id: get-token-expiry-flow\n        request:\n          method: POST\n          path: /oauth/token\n          body_json:\n            grant_type: refresh_token\n        sequence:\n          - status: 401\n            body: '{\"error\":\"token_expired\"}'\n          - status: 200\n            body: '{\"access_token\":\"new-token\",\"expires_in\":3600}'\n        sequence_exhausted: hold_last\n        response:\n          status: 200\n          body: '{\"access_token\":\"new-token\",\"expires_in\":3600}'\n\nscenarios:\n  - id: auth-service-down\n    name: Auth Service Down\n    patches:\n      - mock_id: get-token\n        status: 503\n        body: '{\"error\":\"service unavailable\"}'\n\n  - id: rate-limited\n    name: Rate Limited\n    patches:\n      - mock_id: get-token\n        status: 429\n        body: '{\"error\":\"too_many_requests\"}'\n```\n\n---\n\n## Scenarios\n\nScenarios let you pre-define named mock overrides and activate/deactivate them at any time — great for toggling between happy path and failure modes during testing.\n\n### Define in config\n\n```yaml\nscenarios:\n  - id: payment-timeout\n    name: Payment Gateway Timeout\n    patches:\n      - mock_id: charge\n        status: 504\n        body: '{\"error\":\"timeout\"}'\n        delay: 5s\n      - mock_id: refund\n        disabled: true   # Removes this endpoint entirely\n```\n\n### Control via CLI\n\n```sh\nmockly scenario list\nmockly scenario activate payment-timeout\nmockly scenario deactivate payment-timeout\n```\n\n### Control via API\n\n```sh\n# Activate\ncurl -X POST http://localhost:9091/api/scenarios/payment-timeout/activate\n\n# Deactivate\ncurl -X DELETE http://localhost:9091/api/scenarios/payment-timeout/activate\n\n# List active\ncurl http://localhost:9091/api/scenarios/active\n```\n\n### Fault injection in scenarios\n\nScenarios can bundle protocol faults alongside mock patches — activating a scenario sets both atomically:\n\n```yaml\nscenarios:\n  - id: backend-degraded\n    name: Backend Degraded\n    patches:\n      - mock_id: get-user\n        status: 503\n    faults:\n      redis:\n        error: \"LOADING Redis is loading the dataset in memory\"\n        error_rate: 1.0\n      grpc:\n        code: UNAVAILABLE\n        delay: 500ms\n        error_rate: 0.8\n\n  - id: dns-failure\n    name: DNS Resolution Failure\n    faults:\n      dns:\n        rcode: NXDOMAIN\n        error_rate: 0.5\n```\n\nWhen `backend-degraded` is activated: the `get-user` mock is patched and Redis/gRPC start returning faults. Deactivating the scenario restores normal behaviour.\n\n---\n\n## Fault Injection\n\nInject protocol-native faults to test your application's resilience without touching individual mocks. Each protocol has its own fault shape using native error codes.\n\n### Via CLI\n\n```sh\n# DNS: 50% of queries return NXDOMAIN\nmockly fault set --protocol dns --rcode NXDOMAIN --rate 0.5\n\n# gRPC: always return UNAVAILABLE\nmockly fault set --protocol grpc --code UNAVAILABLE\n\n# Redis: always return LOADING error\nmockly fault set --protocol redis --error \"LOADING\"\n\n# Add 200ms latency to all Kafka requests\nmockly fault set --protocol kafka --delay 200ms\n\n# Clear a specific protocol's fault\nmockly fault clear --protocol dns\n\n# Clear all faults\nmockly fault clear\n```\n\n### Via API\n\n```sh\n# Set DNS fault\ncurl -X POST http://localhost:9091/api/fault/dns \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"rcode\":\"NXDOMAIN\",\"error_rate\":0.5}'\n\n# Set gRPC fault\ncurl -X POST http://localhost:9091/api/fault/grpc \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"code\":\"UNAVAILABLE\",\"delay\":\"500ms\",\"error_rate\":1.0}'\n\n# Get all active faults\ncurl http://localhost:9091/api/fault\n\n# Clear DNS fault only\ncurl -X DELETE http://localhost:9091/api/fault/dns\n\n# Clear all faults\ncurl -X DELETE http://localhost:9091/api/fault\n```\n\n### Fault fields per protocol\n\n| Protocol | Fields | Values / notes |\n|---|---|---|\n| `http` / `graphql` | `status`, `body`, `delay`, `error_rate` | HTTP status code (default 503) |\n| `websocket` | `close_code`, `message`, `delay`, `error_rate` | WS close code (default 1011) |\n| `grpc` | `code`, `message`, `delay`, `error_rate` | `UNAVAILABLE` \\| `NOT_FOUND` \\| `DEADLINE_EXCEEDED` \\| `PERMISSION_DENIED` \\| `RESOURCE_EXHAUSTED` \\| `INTERNAL` |\n| `tcp` | `response`, `delay`, `error_rate` | Send `response` bytes then close (default: just close) |\n| `redis` | `error`, `delay`, `error_rate` | Raw Redis error string e.g. `\"LOADING\"` (default `\"ERR fault injected\"`) |\n| `dns` | `rcode`, `delay`, `error_rate` | `NXDOMAIN` \\| `SERVFAIL` \\| `REFUSED` \\| `NOTIMP` \\| `FORMERR` (default `SERVFAIL`) |\n| `smtp` | `code`, `message`, `delay`, `error_rate` | SMTP code e.g. 421, 450, 550 (default 421) |\n| `imap` | `response`, `message`, `delay`, `error_rate` | `NO` \\| `BAD` \\| `BYE` (default `NO`) |\n| `ftp` | `code`, `message`, `delay`, `error_rate` | FTP code e.g. 421, 530, 550 (default 421) |\n| `ldap` | `result_code`, `message`, `delay`, `error_rate` | LDAP result code: 32=NO_SUCH_OBJECT, 49=INVALID_CREDENTIALS, 50=INSUFFICIENT_ACCESS, 52=UNAVAILABLE (default 52) |\n| `kafka` | `error_code`, `delay`, `error_rate` | Kafka error code: 3=UNKNOWN_TOPIC, 5=LEADER_NOT_AVAILABLE, 7=REQUEST_TIMED_OUT (default 5) |\n| `memcached` | `error_type`, `message`, `delay`, `error_rate` | `SERVER_ERROR` \\| `CLIENT_ERROR` (default `SERVER_ERROR`) |\n| `stomp` | `message`, `delay`, `error_rate` | Sends STOMP ERROR frame |\n| `amqp` | `delay`, `error_rate` | Silently drops message delivery |\n| `mqtt` | `delay`, `error_rate` | Silently drops response publish |\n| `coap` | `code`, `delay`, `error_rate` | CoAP code: `4.01`, `4.03`, `4.04`, `5.00`, `5.03` (default `5.00`) |\n| `sip` | `status`, `reason`, `delay`, `error_rate` | SIP status: 404, 408, 486, 503 (default 503) |\n| `snmp` | `message`, `delay`, `error_rate` | Returns error from OID callback |\n\n`error_rate`: probability 0.0–1.0 that the fault fires; 0 = always (default).\n\n### Via scenarios (recommended for reproducible tests)\n\nSee [Fault injection in scenarios](#fault-injection-in-scenarios).\n\n---\n\n## PATCH Mocks\n\nChange individual fields of an existing mock without replacing it entirely:\n\n```sh\ncurl -X PATCH http://localhost:9091/api/mocks/http/charge \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"response\":{\"status\":500,\"body\":\"{\\\"error\\\":\\\"internal\\\"}\"}}'\n```\n\n---\n\n## Preset Configs\n\nMockly ships with pre-built YAML configs for common services:\n\n| Preset | Description |\n|---|---|\n| `keycloak` | Token endpoint, JWKS, userinfo, introspection |\n| `authelia` | Auth verify, session endpoints |\n| `oauth2` | Generic OAuth2 flows (authorize, token, revoke) |\n| `github` | REST API: repos, issues, pull requests |\n| `stripe` | Charges, refunds, customers, payment intents |\n| `openai` | Chat completions, embeddings, models |\n| `slack` | Messages, channels, users, reactions |\n| `twilio` | SMS, calls, lookup |\n| `sendgrid` | Email send, templates, contacts |\n\nEach preset also includes built-in scenarios for common failure modes (e.g. `keycloak-unauthorized`, `stripe-card-declined`).\n\n### Use a preset\n\n```sh\nmockly preset use keycloak\n```\n\n### Import a preset into your own config\n\n```sh\nmockly preset show keycloak \u003e keycloak.yaml\n# Edit keycloak.yaml, then:\nmockly start --config keycloak.yaml\n```\n\n---\n\n## CLI Reference\n\n```\nmockly start       [--config \u003cfile\u003e] [--http-port \u003cn\u003e] [--api-port \u003cn\u003e]\nmockly apply       --config \u003cfile\u003e\nmockly list\nmockly add http    --method GET --path /foo --status 200 --body '{\"ok\":true}'\nmockly delete      \u003cmock-id\u003e\nmockly status\nmockly reset\nmockly preset      list | show \u003cname\u003e | use \u003cname\u003e\nmockly scenario    list | show \u003cid\u003e | activate \u003cid\u003e | deactivate \u003cid\u003e\nmockly fault       set --protocol \u003cproto\u003e [--delay \u003cd\u003e] [--rate \u003cf\u003e] [protocol-specific flags] | clear [--protocol \u003cproto\u003e] | show\n```\n\n---\n\n## Management API Reference\n\nBase URL: `http://localhost:9091`\n\n\u003e **API documentation files** — two ready-to-use references ship in `docs/`:\n\u003e\n\u003e | File | Format | How to use |\n\u003e |---|---|---|\n\u003e | [`docs/openapi.yaml`](docs/openapi.yaml) | OpenAPI 3.1 | Open in [Swagger UI](https://editor.swagger.io/), Redoc, Stoplight, or any OpenAPI-compatible tool |\n\u003e | [`docs/mockly.postly.json`](docs/mockly.postly.json) | [Postly](https://github.com/dever-labs/postly) collection | Import into Postly and set the `baseUrl` environment variable to your Mockly instance (e.g. `http://localhost:9091`) |\n\n### Protocols\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/protocols` | List all protocol statuses |\n| `GET` | `/api/health` | Health check |\n\n### HTTP Mocks\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/mocks/http` | List HTTP mocks |\n| `POST` | `/api/mocks/http` | Create HTTP mock |\n| `PUT` | `/api/mocks/http/{id}` | Replace HTTP mock |\n| `PATCH` | `/api/mocks/http/{id}` | Partial update HTTP mock |\n| `DELETE` | `/api/mocks/http/{id}` | Delete HTTP mock |\n\nSimilarly for WebSocket (`/api/mocks/websocket`), gRPC (`/api/mocks/grpc`), GraphQL (`/api/mocks/graphql`), TCP (`/api/mocks/tcp`), Redis (`/api/mocks/redis`), SMTP (`/api/mocks/smtp`), MQTT (`/api/mocks/mqtt`), SNMP (`/api/mocks/snmp`), DNS (`/api/mocks/dns`), AMQP (`/api/mocks/amqp`), Kafka (`/api/mocks/kafka`), LDAP (`/api/mocks/ldap`), IMAP (`/api/mocks/imap`), FTP (`/api/mocks/ftp`), Memcached (`/api/mocks/memcached`), STOMP (`/api/mocks/stomp`), CoAP (`/api/mocks/coap`), and SIP (`/api/mocks/sip`).\n\n### Call Verification (HTTP)\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/calls/http/{mockId}` | Get call count + log entries for a mock |\n| `POST` | `/api/calls/http/{mockId}/wait` | Block until mock has been called N times (body: `{\"count\":N,\"timeout\":\"5s\"}`) |\n| `DELETE` | `/api/calls/http/{mockId}` | Clear log entries for mock + reset all HTTP call counts |\n| `DELETE` | `/api/calls/http` | Clear all HTTP log entries and reset all call counts |\n\n### Email inbox (SMTP)\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/emails` | List captured emails |\n| `DELETE` | `/api/emails` | Clear inbox |\n\n### MQTT messages\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/mqtt/messages` | List captured MQTT messages |\n| `DELETE` | `/api/mqtt/messages` | Clear message store |\n\n### Message stores (AMQP, Kafka, STOMP)\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/amqp/messages` | List captured AMQP messages |\n| `DELETE` | `/api/amqp/messages` | Clear AMQP message store |\n| `GET` | `/api/kafka/messages` | List captured Kafka messages |\n| `DELETE` | `/api/kafka/messages` | Clear Kafka message store |\n| `GET` | `/api/stomp/messages` | List captured STOMP messages |\n| `DELETE` | `/api/stomp/messages` | Clear STOMP message store |\n\n### SNMP Mocks \u0026 Traps\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/mocks/snmp` | List configured OID mocks |\n| `POST` | `/api/mocks/snmp` | Add an OID mock |\n| `PUT` | `/api/mocks/snmp/{id}` | Replace an OID mock |\n| `DELETE` | `/api/mocks/snmp/{id}` | Remove an OID mock |\n| `GET` | `/api/snmp/traps` | List configured traps |\n| `POST` | `/api/snmp/traps` | Add a trap config |\n| `POST` | `/api/snmp/traps/{id}/send` | Send a configured trap immediately |\n\n### Scenarios\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/scenarios` | List all scenarios |\n| `POST` | `/api/scenarios` | Create scenario |\n| `GET` | `/api/scenarios/active` | List active scenarios |\n| `GET` | `/api/scenarios/{id}` | Get scenario |\n| `PUT` | `/api/scenarios/{id}` | Replace scenario |\n| `DELETE` | `/api/scenarios/{id}` | Delete scenario |\n| `POST` | `/api/scenarios/{id}/activate` | Activate scenario |\n| `DELETE` | `/api/scenarios/{id}/activate` | Deactivate scenario |\n\n### Fault Injection\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/fault` | Get all active direct faults |\n| `DELETE` | `/api/fault` | Clear all direct faults |\n| `GET` | `/api/fault/{protocol}` | Get fault config for a protocol |\n| `POST` | `/api/fault/{protocol}` | Set fault config for a protocol |\n| `DELETE` | `/api/fault/{protocol}` | Clear fault for a protocol |\n\n`{protocol}` is one of: `http`, `graphql`, `websocket`, `grpc`, `tcp`, `redis`, `mqtt`, `smtp`, `snmp`, `dns`, `amqp`, `kafka`, `ldap`, `imap`, `ftp`, `memcached`, `stomp`, `coap`, `sip`.\n\n### State Store\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/state` | Get all state keys |\n| `POST` | `/api/state` | Set state keys (JSON object) |\n| `DELETE` | `/api/state/{key}` | Delete a state key |\n\n### Logs\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/api/logs` | Get recent log entries |\n| `DELETE` | `/api/logs` | Clear logs |\n| `GET` | `/api/logs/stream` | SSE stream of live log entries |\n\n### Reset\n\n| Method | Path | Description |\n|---|---|---|\n| `POST` | `/api/reset` | Reset all mocks/state/logs/fault/scenarios to config defaults |\n\n---\n\n## Client Libraries\n\nMockly ships official clients that manage the process lifecycle, port allocation, and the management API for you — so tests stay clean and portable.\n\n| Language | Package | Install |\n|---|---|---|\n| **Go** | `github.com/dever-labs/mockly/clients/go` | `go get github.com/dever-labs/mockly/clients/go` |\n| **Node.js / TypeScript** | `@dever-labs/mockly-driver` | `npm i -D @dever-labs/mockly-driver` |\n| **Java** | `io.github.dever-labs:mockly-driver` | See Maven/Gradle below |\n| **.NET / C#** | `Mockly.Driver` | `dotnet add package Mockly.Driver` |\n| **Python** | `mockly-driver` | `pip install mockly-driver` |\n| **Rust** | `mockly-driver` | `mockly-driver = \"0.4\"` in `[dev-dependencies]` |\n\nAll clients:\n- Automatically find or install the Mockly binary for the current platform\n- Allocate two free ports atomically (no TOCTOU races)\n- Retry startup up to 3 times on port conflicts\n- Expose the same concepts: `addMock`, `activateScenario`, `setFault`, `reset`, `stop`\n\n### Go\n\n```go\nimport mocklydriver \"github.com/dever-labs/mockly/clients/go\"\n\nserver, err := mocklydriver.Ensure(mocklydriver.Options{}, mocklydriver.InstallOptions{})\ndefer server.Stop()\n\nserver.AddMock(mocklydriver.Mock{\n    ID:       \"get-user\",\n    Request:  mocklydriver.Request{Method: \"GET\", Path: \"/users/1\"},\n    Response: mocklydriver.Response{Status: 200, Body: `{\"id\":1}`},\n})\n// server.HTTPBase = \"http://127.0.0.1:\u003cport\u003e\"\n```\n\n[→ Full Go docs](docs/clients/go.md)\n\n### Node.js / TypeScript\n\n```ts\nimport { MocklyServer } from '@dever-labs/mockly-driver'\n\nconst server = await MocklyServer.ensure()\nawait server.addMock({\n    id: 'get-user',\n    request: { method: 'GET', path: '/users/1' },\n    response: { status: 200, body: '{\"id\":1}' },\n})\n// server.httpBase = \"http://127.0.0.1:\u003cport\u003e\"\nawait server.stop()\n```\n\n[→ Full Node.js docs](docs/clients/node.md)\n\n### Java\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003eio.github.dever-labs\u003c/groupId\u003e\n  \u003cartifactId\u003emockly-driver\u003c/artifactId\u003e\n  \u003cversion\u003e0.4.7\u003c/version\u003e\n  \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\n```java\ntry (MocklyServer server = MocklyServer.ensure(MocklyConfig.builder().build())) {\n    server.addMock(Mock.builder(\"get-user\",\n        MockRequest.builder(\"GET\", \"/users/1\").build(),\n        MockResponse.builder(200).body(\"{\\\"id\\\":1}\").build()\n    ).build());\n    // server.httpBase = \"http://127.0.0.1:\u003cport\u003e\"\n}\n```\n\n[→ Full Java docs](docs/clients/java.md)\n\n### .NET / C#\n\n```sh\ndotnet add package Mockly.Driver\n```\n\n```csharp\nawait using var server = await MocklyServer.CreateAsync();\nawait server.AddMockAsync(new Mock {\n    Id = \"get-user\",\n    Request  = new MockRequest { Method = \"GET\", Path = \"/users/1\" },\n    Response = new MockResponse { Status = 200, Body = \"\"\"{\"id\":1}\"\"\" },\n});\n// server.HttpBase = \"http://127.0.0.1:\u003cport\u003e\"\n```\n\n[→ Full .NET docs](docs/clients/dotnet.md)\n\n### Python\n\n```sh\npip install mockly-driver\n```\n\n```python\nfrom mockly_driver import MocklyServer, Mock, MockRequest, MockResponse\n\nserver = MocklyServer.ensure()\nserver.add_mock(Mock(\n    id=\"get-user\",\n    request=MockRequest(method=\"GET\", path=\"/users/1\"),\n    response=MockResponse(status=200, body='{\"id\":1}'),\n))\n# server.http_base = \"http://127.0.0.1:\u003cport\u003e\"\nserver.stop()\n```\n\n[→ Full Python docs](docs/clients/python.md)\n\n### Rust\n\n```toml\n[dev-dependencies]\nmockly-driver = \"0.4\"\n```\n\n```rust\nlet mut server = MocklyServer::ensure(ServerOptions::default(), Default::default()).unwrap();\nserver.add_mock(\u0026Mock {\n    id: \"get-user\".into(),\n    request: Request { method: \"GET\".into(), path: \"/users/1\".into(), ..Default::default() },\n    response: Response { status: 200, body: Some(r#\"{\"id\":1}\"#.into()), ..Default::default() },\n}).unwrap();\n// server.http_base = \"http://127.0.0.1:\u003cport\u003e\"\n```\n\n[→ Full Rust docs](docs/clients/rust.md)\n\n---\n\n## CI Integration\n\nMockly is a single static binary with no runtime dependencies — ideal for CI.\n\n### GitHub Actions (composite action)\n\n```yaml\nsteps:\n  - uses: actions/checkout@v5\n\n  - name: Start Mockly\n    uses: dever-labs/mockly/.github/actions/setup-mockly@v0.4.7\n    with:\n      version: v0.4.7          # pin to a specific version\n      config: mockly.yaml      # path to your config\n      api-port: 9090           # management API port (default)\n\n  - name: Run tests\n    run: npm test\n```\n\nThe action automatically:\n- Downloads the right binary for the runner OS/arch\n- Starts mockly in the background\n- Waits up to 30 s for the server to be ready\n- Kills the process after the job completes\n\n---\n\n### GitLab CI\n\nInclude the template and extend the `.mockly-start` job:\n\n```yaml\ninclude:\n  - remote: 'https://raw.githubusercontent.com/dever-labs/mockly/main/.gitlab/mockly.yml'\n\nintegration-tests:\n  extends: .mockly-start\n  variables:\n    MOCKLY_VERSION: \"v0.4.7\"\n    MOCKLY_CONFIG: \"mockly.yaml\"\n  script:\n    - ./run-tests.sh\n```\n\nOr run it as a Docker service (no binary install needed):\n\n```yaml\nintegration-tests:\n  image: alpine:3.21\n  services:\n    - name: ghcr.io/dever-labs/mockly:latest\n      alias: mockly\n      variables:\n        # mount config via CI artifacts or inline\n  variables:\n    MOCKLY_URL: http://mockly:9090\n  script:\n    - apk add --no-cache curl\n    - curl \"$MOCKLY_URL/api/protocols\"\n    - ./run-tests.sh\n```\n\n---\n\n### Any CI (install script)\n\n```sh\n# Install latest release\ncurl -sSfL https://raw.githubusercontent.com/dever-labs/mockly/main/install.sh | bash\n\n# Or pin to a version\nMOCKLY_VERSION=v0.2.0 \\\n  curl -sSfL https://raw.githubusercontent.com/dever-labs/mockly/main/install.sh | bash\n\n# Start in background and wait for ready\nmockly start -c mockly.yaml \u0026\nuntil curl -sf http://localhost:9090/api/protocols; do sleep 1; done\n```\n\n---\n\n### Docker\n\n```sh\n# Run with your local config\ndocker run --rm \\\n  -v \"$PWD/mockly.yaml:/config/mockly.yaml:ro\" \\\n  -p 8080:8080 -p 9090:9090 \\\n  ghcr.io/dever-labs/mockly:latest\n\n# Or with docker compose\ndocker compose up\n```\n\n---\n\n## Architecture\n\n```\n┌──────────────────────────────────────────────────────────────────────────────────────────┐\n│                                Single Binary                                             │\n│                                                                                          │\n│  ┌─────────────────────────────────────────────────────────────────────┐                 │\n│  │                     Management API + Web UI  :9091                  │                 │\n│  │  CRUD mocks/rules  ·  scenarios  ·  fault  ·  state  ·  logs/SSE    │                 │\n│  └─────────────────────────────────────────────────────────────────────┘                 │\n│                                                                                          │\n│  ┌──────┐ ┌─────────┐ ┌──────┐ ┌─────────┐ ┌─────┐ ┌───────┐ ┌──────┐ ┌──────┐ ┌──────┐  │\n│  │ HTTP │ │WebSocket│ │ gRPC │ │GraphQL  │ │ TCP │ │ Redis │ │ SMTP │ │ MQTT │ │ SNMP │  │\n│  │:8080 │ │ :8081   │ │:50051│ │ :8082   │ │:8083│ │ :6379 │ │:2525 │ │:1883 │ │:1161 │  │\n│  └──────┘ └─────────┘ └──────┘ └─────────┘ └─────┘ └───────┘ └──────┘ └──────┘ └──────┘  │\n│                                                                                          │\n│  Shared:  State Store  ·  Request Logger  ·  Scenario Store                              │\n└──────────────────────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Development\n\n```sh\nmake build        # build UI + Go binary\nmake test         # run unit + integration tests\nmake test-e2e     # run e2e tests (builds binary first)\nmake lint         # run golangci-lint\nmake dev          # hot-reload with air\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for full setup instructions, commit conventions, and the PR process.\n\n---\n\n## Contributing\n\nContributions are welcome — bug reports, feature requests, preset configs, and code.\n\nPlease read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.\nBy participating you agree to follow the [Code of Conduct](CODE_OF_CONDUCT.md).\n\nFor security issues, follow the process in [SECURITY.md](SECURITY.md) — **do not open a public issue**.\n\n---\n\n## License\n\nCopyright © 2026 dever-labs. Released under the [MIT License](LICENSE).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdever-labs%2Fmockly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdever-labs%2Fmockly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdever-labs%2Fmockly/lists"}