MAXCONN
Leia em Português.
Zero-dependency network automation toolkit for Python: SSH/Telnet clients built on raw sockets (no Paramiko/Netmiko/Scrapli), a CLI for day-to-day network engineering tasks, and a themeable interactive terminal.
Quick Start
pip install maxconn
maxconn hosts add olt-01 --host 10.0.0.1 --port 22 --protocol ssh --username admin
maxconn ssh olt-01 --command "show version"
Prefer Python? Same idea, three lines:
import maxconn
with maxconn.connect("192.0.2.10", protocol="ssh", username="admin", password="secret") as conn:
print(conn.run("display version", prompt_markers=(">", "#")).text)
Why MAXCONN
MAXCONN is meant to grow into a practical toolkit for network engineers and DevOps engineers who automate network tasks: connecting to devices, running commands, reading output, collecting data, validating state, building inventory, and later adding vendor-specific modules.
The project starts with the connection layer. Today MAXCONN has SSH and Telnet clients built on top of sockets, without using Paramiko, Netmiko, Scrapli, or Telnetlib as runtime clients.
Package on PyPI: https://pypi.org/project/maxconn/
Installation
Regular install:
pip install maxconn
For SSH:
pip install "maxconn[ssh]"
Telnet does not pull extra runtime dependencies. SSH uses cryptography
through the ssh extra. Paramiko is test-only and is used to run a local SSH
server for integration tests.
Development install:
git clone https://github.com/mmaxjr/maxconn
cd maxconn
pip install -e ".[dev]"
pytest -v
ruff check src tests
Current development version: 0.3.2.
Module Status
| Area | Status | Interface |
|---|---|---|
| SSH/Telnet | basic usage | Python API and CLI |
| Ping/scan/traceroute | basic usage | Python API and CLI |
| MTR | basic live table | Python API and CLI |
| SNMP v2c GET/WALK | basic usage | Python API and CLI |
| SFTP | basic file operations | Python API and CLI |
| HTTP/FTP | small client | Python API |
CLI Reference
Connection & saved hosts
maxconn ssh 192.0.2.10 --username admin --password secret --command "show version"
maxconn telnet 192.0.2.20 --username admin --password secret --command "show status"
maxconn ssh olt-01 # interactive session (no --command)
maxconn hosts add olt-01 --host 10.0.0.1 --port 22 --protocol ssh --username admin --profile huawei --tags olt,pop-centro
maxconn hosts list
maxconn hosts list --json
maxconn hosts show olt-01
maxconn hosts edit olt-01 --host 10.0.0.2 # alias: hosts set
maxconn hosts remove olt-01
maxconn hosts test olt-01
maxconn hosts test --all
maxconn hosts test --tag core
maxconn hosts export --file hosts-backup.json
maxconn hosts import --file hosts-backup.json
maxconn hosts run --all --command "show version"
maxconn hosts run --tag core --command "show version" --json
maxconn hosts recent
maxconn hosts save-recent 1 --name olt-01 --profile huawei --tags olt
maxconn start # themeable interactive terminal
Shell completion (bash/zsh/PowerShell) for commands and flags:
source <(maxconn completion bash) # add to ~/.bashrc
source <(maxconn completion zsh) # add to ~/.zshrc
maxconn completion powershell | Out-String | Invoke-Expression # add to $PROFILE
Local defaults, so you don't have to repeat --timeout/--concurrency/--workers/--ports on every call:
maxconn config set timeout 5
maxconn config set ports 22,80,443
maxconn config get timeout
maxconn config list
maxconn config unset timeout
Config backup & diff
maxconn backup olt-01 # uses the saved host's profile for the default command
maxconn backup 192.0.2.10 --username admin --password secret --command "show running-config" --to backup.cfg
maxconn diff backup-2026-08-01.cfg backup-2026-08-16.cfg # exit code 1 if they differ
maxconn diff backup-2026-08-01.cfg backup-2026-08-16.cfg --json
Backups default to ~/.maxconn/backups/<host>/<timestamp>.cfg unless --to PATH is given. The default backup command is looked up from the saved host's --profile (cisco, huawei, mikrotik); pass --command explicitly for anything else.
Inventory
maxconn inventory
maxconn inventory --json
maxconn inventory --output csv --export inventory.csv
maxconn inventory --reconcile 192.168.0.0/24 # planned (saved hosts) vs. provisioned (live scan)
maxconn inventory --reconcile 192.168.0.0/24 --json
--reconcile runs a live discover scan of the given network and reports saved hosts that were not seen reachable ("documented but unreachable") alongside reachable hosts that aren't in your saved inventory ("undocumented"). Exit code is 1 when there's drift, 0 when everything matches - useful for scripted checks.
Discovery
maxconn discover 192.168.0.0/24
maxconn discover 192.168.0.0/24 --ports 80,443 --json
maxconn discover 192.168.0.0/24 --only-open
maxconn discover 192.168.0.0/24 --save-found --name-prefix sw --tags discovered,lab
maxconn discover 10.0.0.0/20 --confirm # required above the host-count threshold
Diagnostics
maxconn ping 192.0.2.1
maxconn ping 192.0.2.1 --output json --export ping.json
maxconn scan 192.0.2.1 --ports 22,23,80,443
maxconn traceroute 8.8.8.8
maxconn mtr 8.8.8.8 --count 5 --interval 1
maxconn snmp get 192.0.2.1 1.3.6.1.2.1.1.5.0 --community public
maxconn snmp walk 192.0.2.1 1.3.6.1.2.1.1 --community public
maxconn doctor
maxconn doctor --network # + DNS/gateway/internet/PyPI-version checks
maxconn history list --limit 20 --since today
maxconn history show 1
maxconn history replay 1
maxconn history clear
maxconn selftest
maxconn config set audit_log on # persist the audit trail to ~/.maxconn/audit.jsonl
maxconn audit tail
maxconn audit tail -n 50 --json
maxconn config set update_notify on # a passive "new version available" hint after commands
update_notify checks PyPI at most once every 24h (cached in ~/.maxconn/update_check.json) and only prints a one-line notice to stderr when a newer version exists - it never blocks or fails a command, even if the check itself fails.
File transfer (SFTP)
maxconn sftp ls 192.0.2.10 /configs --username admin --password secret
maxconn sftp get 192.0.2.10 /remote/startup.cfg ./startup.cfg --username admin --password secret
maxconn sftp put 192.0.2.10 ./backup.cfg /remote/backup.cfg --username admin --password secret
maxconn sftp stat 192.0.2.10 /remote/startup.cfg --username admin --password secret
maxconn sftp mkdir 192.0.2.10 /remote/new-folder --username admin --password secret
maxconn sftp rm 192.0.2.10 /remote/old.cfg --username admin --password secret
maxconn sftp rename 192.0.2.10 /remote/a.cfg /remote/b.cfg --username admin --password secret
Saved hosts live in ~/.maxconn/hosts.json. Recently used hosts live in
~/.maxconn/seen_hosts.json, without passwords. To save a password locally,
use --save-password explicitly; it is never printed, and hosts list
only shows a yes/no indicator for whether one is saved.
Local command history lives in ~/.maxconn/history.jsonl; commands
containing words such as password, token, or secret are stored with
redaction.
To enter a device terminal, run maxconn ssh NAME or maxconn telnet NAME
without --command. Inside the visual shell opened by maxconn start, use
ssh NAME, telnet NAME, or open NAME.
Python API
Basic Usage
Telnet:
import maxconn
with maxconn.connect(
"192.0.2.20",
protocol="telnet",
username="admin",
password="secret",
) as conn:
result = conn.run("show status", prompt_markers=(">", "#"))
print(result.text)
SSH:
import maxconn
with maxconn.connect(
"192.0.2.30",
protocol="ssh",
username="admin",
password="secret",
) as conn:
result = conn.run("show version", prompt_markers=(">", "#"))
print(result.text)
For lower-level use, Connection.send(), Connection.recv(),
Connection.read_until(), and Connection.send_command() are still available.
Command Result
Connection.run() returns a result object:
result = conn.run("display version", prompt_markers=(">", "#"))
print(result.command)
print(result.text)
print(result.bytes)
print(result.elapsed)
print(result.exit_status)
print(result.ok)
result.ok is true when exit_status is None or 0. Interactive CLI
sessions, such as Telnet and shell-style SSH, usually do not provide an exit
status, so None is expected.
Expect
For prompt-based automation, use ExpectSession directly:
from maxconn.automation import ExpectSession, PromptProfile
expect = ExpectSession(conn, prompt_markers=PromptProfile.CISCO)
output = expect.run("show running-config", timeout=20.0)
ExpectSession handles the common parts of a network device CLI:
- waits for prompts
- strips command echo
- answers simple pagination markers such as
--More-- - includes partial output in timeout errors
- answers simple confirmation prompts such as
[Y/N]
Sessions and Ping
SessionManager controls named connections:
import maxconn
manager = maxconn.SessionManager(defaults={"protocol": "ssh", "username": "admin"})
conn = manager.connect("olt-01", "192.0.2.10", password="secret")
result = conn.run("display version", prompt_markers=(">", "#"))
manager.close_all()
Basic ping:
import maxconn
result = maxconn.ping("192.0.2.1")
print(result.reachable)
TCP scan:
import maxconn
for result in maxconn.scan("192.0.2.1", ports=[22, 23, 80, 443]):
print(result.port, "open" if result.open else "closed")
Subnet discovery:
import maxconn
for host in maxconn.discover("192.168.0.0/24"):
if host.reachable:
print(host.host, host.open_ports, host.banner)
In the terminal, maxconn discover NETWORK/CIDR tests common TCP ports across
the subnet. The default ports include at least 80 and 443, plus common
network ports such as SSH, Telnet, SNMP, MikroTik, and alternate HTTP/HTTPS.
Use --ports to limit or change the list. Networks above the host-count
threshold require --confirm (or confirm=True in Python).
Traceroute and mini MTR:
import maxconn
trace = maxconn.traceroute("8.8.8.8")
for hop in trace.hops:
print(hop.hop, hop.address)
report = maxconn.mtr("8.8.8.8", count=5)
print(report.loss_percent, report.avg)
In the terminal, maxconn mtr HOST runs continuously and refreshes a table per
hop. Stop it with Ctrl+C. For a bounded run, pass --count. Hops that do not
answer are shown as No response from host, so the path is not hidden and the
internal * marker does not leak into the table.
By default the route is discovered once and known hops are measured every round,
which makes refreshes closer to WinMTR. On networks with many silent hops,
increase --trace-timeout. To refresh the route periodically, use
--rediscover-every N.
For automation and reports, use --json, --output json, --export path.txt, and --no-clear.
Examples
The examples/ folder has small scripts that can be used as starting points:
ssh_run_command.pysftp_backup.pymtr_report.pysnmp_walk.pyscan_ports.py
Before publishing a version, run:
python scripts/release_check.py
HTTP and FTP
Basic HTTP/HTTPS:
from maxconn.protocol.http import HTTPClient
response = HTTPClient(timeout=5.0).get("https://example.com")
print(response.status_code)
print(response.text)
Basic FTP:
from maxconn.protocol.ftp import FTPClient
with FTPClient.connect(
"192.0.2.40",
username="user",
password="secret",
) as ftp:
print(ftp.list())
data = ftp.download("backup.cfg")
Initial SFTP:
import maxconn
sftp = maxconn.connect_sftp(
"192.0.2.40",
username="user",
password="secret",
)
try:
print(sftp.listdir("/configs"))
print(sftp.stat("/configs/startup.cfg"))
sftp.download("/configs/startup.cfg", "startup.cfg")
sftp.upload("backup.cfg", "/configs/backup.cfg")
sftp.mkdir("/configs/archive")
sftp.rename("/configs/backup.cfg", "/configs/archive/backup.cfg")
sftp.remove("/configs/archive/old.cfg")
finally:
sftp.close()
Basic SNMP v2c:
from maxconn.protocol.snmp import SNMPClient
snmp = SNMPClient("192.0.2.1", community="public")
hostname = snmp.get("1.3.6.1.2.1.1.5.0")
print(hostname.value)
for item in snmp.walk("1.3.6.1.2.1.1"):
print(item.oid, item.value)
Timeouts
connect() accepts separate timeouts:
conn = maxconn.connect(
"192.0.2.30",
protocol="ssh",
username="admin",
password="secret",
connect_timeout=5.0,
auth_timeout=10.0,
command_timeout=5.0,
prompt_timeout=10.0,
)
The older timeout= argument still works. When connect_timeout or
auth_timeout is not provided, timeout= is used as the default.
Logging
Command execution writes audit events through the maxconn.audit logger:
import logging
logging.basicConfig(level=logging.INFO)
Command fragments with words such as password, secret, token, or key
are redacted before logging.
Errors
Use the project exception hierarchy:
import maxconn
try:
with maxconn.connect(
"192.0.2.30",
protocol="ssh",
username="admin",
password="bad-password",
) as conn:
print(conn.run("show status", prompt_markers=(">", "#")).text)
except maxconn.AuthenticationError:
print("Login failed")
except maxconn.ConnectionTimeoutError:
print("Connection timed out")
except maxconn.ProtocolError as exc:
print(f"Protocol problem: {exc}")
except maxconn.MaxConnError as exc:
print(f"maxconn error: {exc}")
Project Direction
- Do not turn the project into a wrapper around Paramiko, Netmiko, Scrapli, or Telnetlib.
- Keep optional dependencies behind extras.
- Keep raw bytes available for code that needs them.
- Keep the common API simple.
- Test against local Telnet and SSH servers when it makes sense.
- Publish new versions to PyPI by tag, using GitHub Actions and Trusted Publishing.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file maxconn-0.3.2.tar.gz.
File metadata
- Download URL: maxconn-0.3.2.tar.gz
- Upload date:
- Size: 121.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a8b35f76671587caa2ef00b9a030869fe4f4496a17d4370fd65f1bc0c8bf790
|
|
| MD5 |
20a14400f6dbbbd74c49fdc0f4cf6137
|
|
| BLAKE2b-256 |
1ae60f916823141e4db84c9a570212ec4bc1e2926824fbe6713b900fceb8c980
|
Provenance
The following attestation bundles were made for maxconn-0.3.2.tar.gz:
Publisher:
publish.yml on mmaxjr/maxconn
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
maxconn-0.3.2.tar.gz -
Subject digest:
1a8b35f76671587caa2ef00b9a030869fe4f4496a17d4370fd65f1bc0c8bf790 - Sigstore transparency entry: 2531879922
- Sigstore integration time:
-
Permalink:
mmaxjr/maxconn@960ae63b8ca0cb525c349f393395200e1ad068ea -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/mmaxjr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@960ae63b8ca0cb525c349f393395200e1ad068ea -
Trigger Event:
push
-
Statement type:
File details
Details for the file maxconn-0.3.2-py3-none-any.whl.
File metadata
- Download URL: maxconn-0.3.2-py3-none-any.whl
- Upload date:
- Size: 92.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a36f614e245fad970b36020bb6930c5d8bf1fb4d331be3772364feb60936b2fc
|
|
| MD5 |
fb01ef8aec716f33b1fcbb266503f0e6
|
|
| BLAKE2b-256 |
7868ee344a1747fdf99313745fe8275e4d93fe004ba3f756b2f5f6f8fcdcafa2
|
Provenance
The following attestation bundles were made for maxconn-0.3.2-py3-none-any.whl:
Publisher:
publish.yml on mmaxjr/maxconn
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
maxconn-0.3.2-py3-none-any.whl -
Subject digest:
a36f614e245fad970b36020bb6930c5d8bf1fb4d331be3772364feb60936b2fc - Sigstore transparency entry: 2531880004
- Sigstore integration time:
-
Permalink:
mmaxjr/maxconn@960ae63b8ca0cb525c349f393395200e1ad068ea -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/mmaxjr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@960ae63b8ca0cb525c349f393395200e1ad068ea -
Trigger Event:
push
-
Statement type: