argonaut-mcp-server 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- argonaut_mcp_server-0.1.1/.github/workflows/publish.yml +37 -0
- argonaut_mcp_server-0.1.1/.gitignore +15 -0
- argonaut_mcp_server-0.1.1/AGENTS.md +34 -0
- argonaut_mcp_server-0.1.1/CLAUDE.md +1 -0
- argonaut_mcp_server-0.1.1/LICENSE +21 -0
- argonaut_mcp_server-0.1.1/PKG-INFO +161 -0
- argonaut_mcp_server-0.1.1/README.md +118 -0
- argonaut_mcp_server-0.1.1/argonaut/__init__.py +8 -0
- argonaut_mcp_server-0.1.1/argonaut/adapters/__init__.py +64 -0
- argonaut_mcp_server-0.1.1/argonaut/adapters/aws.py +497 -0
- argonaut_mcp_server-0.1.1/argonaut/adapters/base.py +151 -0
- argonaut_mcp_server-0.1.1/argonaut/adapters/kafka.py +512 -0
- argonaut_mcp_server-0.1.1/argonaut/adapters/rabbitmq.py +354 -0
- argonaut_mcp_server-0.1.1/argonaut/adapters/redis_streams.py +370 -0
- argonaut_mcp_server-0.1.1/argonaut/config/__init__.py +21 -0
- argonaut_mcp_server-0.1.1/argonaut/config/schema.py +209 -0
- argonaut_mcp_server-0.1.1/argonaut/core/__init__.py +37 -0
- argonaut_mcp_server-0.1.1/argonaut/core/models.py +194 -0
- argonaut_mcp_server-0.1.1/argonaut/errors.py +87 -0
- argonaut_mcp_server-0.1.1/argonaut/formatter.py +106 -0
- argonaut_mcp_server-0.1.1/argonaut/policy/__init__.py +18 -0
- argonaut_mcp_server-0.1.1/argonaut/policy/audit.py +56 -0
- argonaut_mcp_server-0.1.1/argonaut/policy/confirm.py +64 -0
- argonaut_mcp_server-0.1.1/argonaut/policy/engine.py +160 -0
- argonaut_mcp_server-0.1.1/argonaut/policy/globs.py +44 -0
- argonaut_mcp_server-0.1.1/argonaut/policy/redaction.py +127 -0
- argonaut_mcp_server-0.1.1/argonaut/server.py +533 -0
- argonaut_mcp_server-0.1.1/argonaut.example.yaml +59 -0
- argonaut_mcp_server-0.1.1/pyproject.toml +68 -0
- argonaut_mcp_server-0.1.1/tests/__init__.py +0 -0
- argonaut_mcp_server-0.1.1/tests/contract/__init__.py +0 -0
- argonaut_mcp_server-0.1.1/tests/contract/backend_aws.py +83 -0
- argonaut_mcp_server-0.1.1/tests/contract/backends.py +114 -0
- argonaut_mcp_server-0.1.1/tests/contract/conftest.py +20 -0
- argonaut_mcp_server-0.1.1/tests/contract/test_contract.py +137 -0
- argonaut_mcp_server-0.1.1/tests/integration/__init__.py +0 -0
- argonaut_mcp_server-0.1.1/tests/integration/test_kafka_integration.py +64 -0
- argonaut_mcp_server-0.1.1/tests/unit/__init__.py +0 -0
- argonaut_mcp_server-0.1.1/tests/unit/test_aws_adapter.py +121 -0
- argonaut_mcp_server-0.1.1/tests/unit/test_config.py +100 -0
- argonaut_mcp_server-0.1.1/tests/unit/test_confirm.py +50 -0
- argonaut_mcp_server-0.1.1/tests/unit/test_globs.py +24 -0
- argonaut_mcp_server-0.1.1/tests/unit/test_kafka_adapter.py +240 -0
- argonaut_mcp_server-0.1.1/tests/unit/test_policy_modes.py +14 -0
- argonaut_mcp_server-0.1.1/tests/unit/test_rabbitmq_adapter.py +170 -0
- argonaut_mcp_server-0.1.1/tests/unit/test_redaction.py +56 -0
- argonaut_mcp_server-0.1.1/tests/unit/test_security_fixes.py +159 -0
- argonaut_mcp_server-0.1.1/tests/unit/test_server_smoke.py +32 -0
- argonaut_mcp_server-0.1.1/uv.lock +1445 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: astral-sh/setup-uv@v3
|
|
14
|
+
- run: uv sync --all-extras --dev
|
|
15
|
+
- run: uv run pytest
|
|
16
|
+
|
|
17
|
+
publish:
|
|
18
|
+
needs: test
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
environment: pypi
|
|
21
|
+
permissions:
|
|
22
|
+
id-token: write
|
|
23
|
+
contents: read
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: astral-sh/setup-uv@v3
|
|
27
|
+
- run: uv build
|
|
28
|
+
- name: Confere se a versão do pacote bate com a tag
|
|
29
|
+
run: |
|
|
30
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
31
|
+
wheel=$(ls dist/*.whl | head -n1)
|
|
32
|
+
pkg_version=$(echo "$wheel" | sed -E 's/.*-([0-9][^-]*)-[^-]+-[^-]+-[^-]+\.whl/\1/')
|
|
33
|
+
if [ "$tag" != "$pkg_version" ]; then
|
|
34
|
+
echo "Tag v$tag não bate com a versão do pacote ($pkg_version) — corrija o pyproject.toml antes de criar a tag."
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Argonaut
|
|
2
|
+
|
|
3
|
+
Servidor MCP (stdio) em Python para descobrir, inspecionar e diagnosticar filas e tópicos (Redis Streams, Kafka, RabbitMQ, SQS, SNS). Irmão do Nautilus (bancos de dados).
|
|
4
|
+
|
|
5
|
+
> Projeto novo: o esqueleto de código ainda não existe. Os comandos abaixo refletem a stack decidida (Python + uv) e passam a valer quando a primeira mudança criar o `pyproject.toml`.
|
|
6
|
+
|
|
7
|
+
<!-- plumb:start -->
|
|
8
|
+
## Comandos
|
|
9
|
+
|
|
10
|
+
- Instalar dependências: `uv sync`
|
|
11
|
+
- Suíte de testes: `uv run pytest`
|
|
12
|
+
- Um único teste: `uv run pytest tests/path/test_x.py::test_nome`
|
|
13
|
+
- Testes de integração (brokers reais): `uv run pytest -m integration` (Testcontainers / LocalStack)
|
|
14
|
+
- Rodar o servidor local (stdio): `uv run argonaut`
|
|
15
|
+
|
|
16
|
+
Convenções do Plumb: commits `tipo(escopo): descrição em pt-BR`, branches `feature/<nome>` a partir de `master`; commit por tarefa: sim. Mensagens ao usuário e erros em pt-BR; logging sempre em `stderr` (stdout é o canal do MCP stdio).
|
|
17
|
+
|
|
18
|
+
Worktree: rodar `uv sync` no worktree novo; sem porta, banco ou serviço a ajustar (conexões vêm de variáveis de ambiente).
|
|
19
|
+
|
|
20
|
+
## Workflow
|
|
21
|
+
Todo pedido segue o Plumb (skill `plumb`): pergunta responde direto depois de
|
|
22
|
+
consultar o segundo cérebro; correção pequena vai pela trilha direta; mudança
|
|
23
|
+
padrão ou profunda começa com o grill (todas as perguntas de uma vez, com a
|
|
24
|
+
recomendação primeiro, sempre pela ferramenta de perguntas), vira uma spec (objetivo,
|
|
25
|
+
resultados esperados observáveis, fases com critério de saída) aprovada antes
|
|
26
|
+
de codar — com o segundo cérebro, guarde-a como item `spec/<id>` — e roda em um
|
|
27
|
+
worktree próprio, um por mudança. Escreva os testes primeiro; peça confirmação
|
|
28
|
+
antes de push, PR ou merge. Em qualquer mudança, até um typo: rode os testes e o
|
|
29
|
+
lint afetados e reporte a evidência — nunca diga "pronto" sem isso. Falta
|
|
30
|
+
documentação atual de uma biblioteca: `plumb-find-docs`; falta acesso a um
|
|
31
|
+
sistema: `plumb-find-mcps`; falta uma competência: `plumb-find-skills` — a
|
|
32
|
+
recomendação vai ao usuário e nada se instala sem o "sim". O que durar (regra,
|
|
33
|
+
decisão, procedimento) vai para o cérebro, não para arquivos.
|
|
34
|
+
<!-- plumb:end -->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KauaLealz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: argonaut-mcp-server
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Servidor MCP para descobrir, inspecionar e diagnosticar filas e tópicos (Redis Streams, Kafka, RabbitMQ, SQS, SNS).
|
|
5
|
+
Author: KauaLealz
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: diagnostics,kafka,mcp,message-queue,model-context-protocol,observability,rabbitmq,redis-streams,sns,sqs
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: System :: Monitoring
|
|
17
|
+
Classifier: Topic :: System :: Systems Administration
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: mcp<2,>=1.6
|
|
20
|
+
Requires-Dist: pydantic>=2.0
|
|
21
|
+
Requires-Dist: pyyaml>=6.0
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: boto3>=1.34; extra == 'all'
|
|
24
|
+
Requires-Dist: confluent-kafka>=2.3; extra == 'all'
|
|
25
|
+
Requires-Dist: redis>=5.0; extra == 'all'
|
|
26
|
+
Requires-Dist: requests>=2.31; extra == 'all'
|
|
27
|
+
Provides-Extra: aws
|
|
28
|
+
Requires-Dist: boto3>=1.34; extra == 'aws'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: boto3>=1.34; extra == 'dev'
|
|
31
|
+
Requires-Dist: fakeredis>=2.21; extra == 'dev'
|
|
32
|
+
Requires-Dist: moto[cloudwatch,sns,sqs]>=5.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: redis>=5.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: requests>=2.31; extra == 'dev'
|
|
36
|
+
Provides-Extra: kafka
|
|
37
|
+
Requires-Dist: confluent-kafka>=2.3; extra == 'kafka'
|
|
38
|
+
Provides-Extra: rabbitmq
|
|
39
|
+
Requires-Dist: requests>=2.31; extra == 'rabbitmq'
|
|
40
|
+
Provides-Extra: redis
|
|
41
|
+
Requires-Dist: redis>=5.0; extra == 'redis'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# Argonaut
|
|
45
|
+
|
|
46
|
+
Servidor **MCP (stdio)** em Python que deixa um agente **descobrir, inspecionar e
|
|
47
|
+
diagnosticar** filas e tópicos em **Redis Streams, Kafka, RabbitMQ, Amazon SQS e
|
|
48
|
+
Amazon SNS**. As operações de escrita existem para resolver o problema encontrado,
|
|
49
|
+
sempre atrás de modo, allowlist e confirmação em duas etapas.
|
|
50
|
+
|
|
51
|
+
Faz par com o [Nautilus](https://github.com/KauaLealz/nautilus-mcp-server) (MCP de
|
|
52
|
+
bancos de dados): Nautilus para bases, Argonaut para mensageria.
|
|
53
|
+
|
|
54
|
+
## O que faz
|
|
55
|
+
|
|
56
|
+
O caso de uso central é diagnóstico: *"por que essa fila está crescendo?"*,
|
|
57
|
+
*"qual consumer group está atrasado?"*, *"o que tem na DLQ?"*, *"por que a mensagem
|
|
58
|
+
publicada no SNS não chegou na fila X?"*.
|
|
59
|
+
|
|
60
|
+
## Instalação e execução
|
|
61
|
+
|
|
62
|
+
Requer **Python 3.11+**. Dependências com [uv](https://docs.astral.sh/uv/).
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv sync --extra all # ou --extra redis / kafka / rabbitmq / aws
|
|
66
|
+
ARGONAUT_CONFIG=./argonaut.yaml uv run argonaut
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
O servidor fala por **stdio**. Configure-o no seu cliente MCP apontando o comando
|
|
70
|
+
para `uv run argonaut` (ou o console script `argonaut`) com a variável de ambiente
|
|
71
|
+
`ARGONAUT_CONFIG` e os segredos que o YAML referencia.
|
|
72
|
+
|
|
73
|
+
## Configuração
|
|
74
|
+
|
|
75
|
+
Um único arquivo YAML descreve defaults e conexões. Segredos **sempre** por
|
|
76
|
+
`${VAR}` (resolvidos do ambiente) — nunca valores no arquivo. Veja
|
|
77
|
+
[`argonaut.example.yaml`](./argonaut.example.yaml). O servidor valida na
|
|
78
|
+
inicialização e falha cedo, com mensagens claras.
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
defaults:
|
|
82
|
+
peek: { max_messages: 20, max_payload_bytes: 4096 }
|
|
83
|
+
confirm_token_ttl: 120s
|
|
84
|
+
audit_log: ./argonaut-audit.jsonl
|
|
85
|
+
redact:
|
|
86
|
+
json_paths: ["$.customer.document", "$.card.*"]
|
|
87
|
+
patterns: [cpf, email, credit_card]
|
|
88
|
+
|
|
89
|
+
connections:
|
|
90
|
+
redis-dev:
|
|
91
|
+
type: redis_streams
|
|
92
|
+
url: "${REDIS_URL}"
|
|
93
|
+
mode: admin
|
|
94
|
+
dlq_convention: "{name}:dlq"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Tipos de conexão: `redis_streams`, `kafka`, `rabbitmq`, `aws` (SQS + SNS juntos).
|
|
98
|
+
|
|
99
|
+
## Tools
|
|
100
|
+
|
|
101
|
+
| Tool | Modo | O que faz |
|
|
102
|
+
|------|------|-----------|
|
|
103
|
+
| `list_connections` | readonly | Conexões configuradas, com tipo e modo |
|
|
104
|
+
| `list_destinations` | readonly | Filas, tópicos e streams (filtro por glob) |
|
|
105
|
+
| `describe_destination` | readonly | Atributos nativos, DLQ e configuração |
|
|
106
|
+
| `get_stats` | readonly | Profundidade, em voo, idade da mais antiga, taxas |
|
|
107
|
+
| `list_consumer_groups` | readonly | Grupos, membros e estado |
|
|
108
|
+
| `get_consumer_lag` | readonly | Lag por grupo e partição |
|
|
109
|
+
| `get_topology` | readonly | Bindings do RabbitMQ e subscriptions do SNS |
|
|
110
|
+
| `peek_messages` | readonly¹ | Leitura sem consumir |
|
|
111
|
+
| `get_message` | readonly | Uma mensagem por posição (offset, id) |
|
|
112
|
+
| `publish_message` | operator | Publica uma mensagem |
|
|
113
|
+
| `redrive_dlq` | operator | Move mensagens da DLQ de volta à origem |
|
|
114
|
+
| `reset_offsets` | operator | Reposiciona um consumer group |
|
|
115
|
+
| `reclaim_pending` | operator | Reatribui mensagens pendentes travadas (Redis) |
|
|
116
|
+
| `purge` | admin | Esvazia um destino |
|
|
117
|
+
| `create_destination` / `delete_destination` | admin | Cria/remove destinos |
|
|
118
|
+
| `update_attributes` | admin | Altera retenção, visibility timeout, políticas |
|
|
119
|
+
| `delete_consumer_group` | admin | Remove um grupo inativo |
|
|
120
|
+
|
|
121
|
+
¹ `peek_messages` com `accept_side_effects=true` (leitura que altera estado, como
|
|
122
|
+
em SQS e filas clássicas do RabbitMQ) exige modo **operator**.
|
|
123
|
+
|
|
124
|
+
Além das tools, o servidor expõe o resource `argonaut://{conexão}/{destino}` (a
|
|
125
|
+
descrição do destino em JSON) e prompts de diagnóstico prontos (`investigar_backlog`,
|
|
126
|
+
`analisar_dlq`, `rastrear_mensagem_sns`).
|
|
127
|
+
|
|
128
|
+
## Segurança
|
|
129
|
+
|
|
130
|
+
O padrão é **readonly**. Toda escrita exige três coisas:
|
|
131
|
+
|
|
132
|
+
1. **Modo por conexão** (readonly ⊂ operator ⊂ admin): produção nasce readonly;
|
|
133
|
+
promover é mudança de config, nunca de runtime.
|
|
134
|
+
2. **Allowlist/denylist por glob**: destino negado não aparece nem na listagem.
|
|
135
|
+
3. **Confirmação em duas etapas**: a 1ª chamada roda em dry-run e devolve o impacto
|
|
136
|
+
(*"vai apagar 48.213 mensagens de orders.dlq"*) e um token com TTL curto,
|
|
137
|
+
vinculado ao hash dos argumentos; a execução só acontece na 2ª chamada com esse
|
|
138
|
+
token.
|
|
139
|
+
|
|
140
|
+
Além disso: payloads **truncados** e com **dados sensíveis redigidos** antes de
|
|
141
|
+
sair; toda MessageView marcada como `untrusted` (o conteúdo é dado, não instrução);
|
|
142
|
+
**auditoria** JSON lines de toda escrita (inclusive dry-runs); credenciais só por
|
|
143
|
+
ambiente, nunca em respostas, erros ou logs.
|
|
144
|
+
|
|
145
|
+
## Desenvolvimento
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
uv sync --extra dev
|
|
149
|
+
uv run pytest tests/unit tests/contract # sem Docker (fakes)
|
|
150
|
+
uv run pytest -m integration # brokers reais (Docker)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
A peça central de teste é uma **suíte de contrato** parametrizada por adapter: o
|
|
154
|
+
mesmo cenário (criar destino, publicar, stats, peek, lag, redrive, purge) roda em
|
|
155
|
+
todos. Um broker novo só entra quando passa nela.
|
|
156
|
+
|
|
157
|
+
## Fora de escopo na v1
|
|
158
|
+
|
|
159
|
+
Ser consumidor/worker de produção; gestão de usuários e ACLs dos brokers;
|
|
160
|
+
provisionamento de clusters; monitoramento contínuo e alertas; decodificação de
|
|
161
|
+
Avro/Protobuf no Kafka; replay de tópicos FIFO do SNS; transporte HTTP remoto.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Argonaut
|
|
2
|
+
|
|
3
|
+
Servidor **MCP (stdio)** em Python que deixa um agente **descobrir, inspecionar e
|
|
4
|
+
diagnosticar** filas e tópicos em **Redis Streams, Kafka, RabbitMQ, Amazon SQS e
|
|
5
|
+
Amazon SNS**. As operações de escrita existem para resolver o problema encontrado,
|
|
6
|
+
sempre atrás de modo, allowlist e confirmação em duas etapas.
|
|
7
|
+
|
|
8
|
+
Faz par com o [Nautilus](https://github.com/KauaLealz/nautilus-mcp-server) (MCP de
|
|
9
|
+
bancos de dados): Nautilus para bases, Argonaut para mensageria.
|
|
10
|
+
|
|
11
|
+
## O que faz
|
|
12
|
+
|
|
13
|
+
O caso de uso central é diagnóstico: *"por que essa fila está crescendo?"*,
|
|
14
|
+
*"qual consumer group está atrasado?"*, *"o que tem na DLQ?"*, *"por que a mensagem
|
|
15
|
+
publicada no SNS não chegou na fila X?"*.
|
|
16
|
+
|
|
17
|
+
## Instalação e execução
|
|
18
|
+
|
|
19
|
+
Requer **Python 3.11+**. Dependências com [uv](https://docs.astral.sh/uv/).
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uv sync --extra all # ou --extra redis / kafka / rabbitmq / aws
|
|
23
|
+
ARGONAUT_CONFIG=./argonaut.yaml uv run argonaut
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
O servidor fala por **stdio**. Configure-o no seu cliente MCP apontando o comando
|
|
27
|
+
para `uv run argonaut` (ou o console script `argonaut`) com a variável de ambiente
|
|
28
|
+
`ARGONAUT_CONFIG` e os segredos que o YAML referencia.
|
|
29
|
+
|
|
30
|
+
## Configuração
|
|
31
|
+
|
|
32
|
+
Um único arquivo YAML descreve defaults e conexões. Segredos **sempre** por
|
|
33
|
+
`${VAR}` (resolvidos do ambiente) — nunca valores no arquivo. Veja
|
|
34
|
+
[`argonaut.example.yaml`](./argonaut.example.yaml). O servidor valida na
|
|
35
|
+
inicialização e falha cedo, com mensagens claras.
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
defaults:
|
|
39
|
+
peek: { max_messages: 20, max_payload_bytes: 4096 }
|
|
40
|
+
confirm_token_ttl: 120s
|
|
41
|
+
audit_log: ./argonaut-audit.jsonl
|
|
42
|
+
redact:
|
|
43
|
+
json_paths: ["$.customer.document", "$.card.*"]
|
|
44
|
+
patterns: [cpf, email, credit_card]
|
|
45
|
+
|
|
46
|
+
connections:
|
|
47
|
+
redis-dev:
|
|
48
|
+
type: redis_streams
|
|
49
|
+
url: "${REDIS_URL}"
|
|
50
|
+
mode: admin
|
|
51
|
+
dlq_convention: "{name}:dlq"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Tipos de conexão: `redis_streams`, `kafka`, `rabbitmq`, `aws` (SQS + SNS juntos).
|
|
55
|
+
|
|
56
|
+
## Tools
|
|
57
|
+
|
|
58
|
+
| Tool | Modo | O que faz |
|
|
59
|
+
|------|------|-----------|
|
|
60
|
+
| `list_connections` | readonly | Conexões configuradas, com tipo e modo |
|
|
61
|
+
| `list_destinations` | readonly | Filas, tópicos e streams (filtro por glob) |
|
|
62
|
+
| `describe_destination` | readonly | Atributos nativos, DLQ e configuração |
|
|
63
|
+
| `get_stats` | readonly | Profundidade, em voo, idade da mais antiga, taxas |
|
|
64
|
+
| `list_consumer_groups` | readonly | Grupos, membros e estado |
|
|
65
|
+
| `get_consumer_lag` | readonly | Lag por grupo e partição |
|
|
66
|
+
| `get_topology` | readonly | Bindings do RabbitMQ e subscriptions do SNS |
|
|
67
|
+
| `peek_messages` | readonly¹ | Leitura sem consumir |
|
|
68
|
+
| `get_message` | readonly | Uma mensagem por posição (offset, id) |
|
|
69
|
+
| `publish_message` | operator | Publica uma mensagem |
|
|
70
|
+
| `redrive_dlq` | operator | Move mensagens da DLQ de volta à origem |
|
|
71
|
+
| `reset_offsets` | operator | Reposiciona um consumer group |
|
|
72
|
+
| `reclaim_pending` | operator | Reatribui mensagens pendentes travadas (Redis) |
|
|
73
|
+
| `purge` | admin | Esvazia um destino |
|
|
74
|
+
| `create_destination` / `delete_destination` | admin | Cria/remove destinos |
|
|
75
|
+
| `update_attributes` | admin | Altera retenção, visibility timeout, políticas |
|
|
76
|
+
| `delete_consumer_group` | admin | Remove um grupo inativo |
|
|
77
|
+
|
|
78
|
+
¹ `peek_messages` com `accept_side_effects=true` (leitura que altera estado, como
|
|
79
|
+
em SQS e filas clássicas do RabbitMQ) exige modo **operator**.
|
|
80
|
+
|
|
81
|
+
Além das tools, o servidor expõe o resource `argonaut://{conexão}/{destino}` (a
|
|
82
|
+
descrição do destino em JSON) e prompts de diagnóstico prontos (`investigar_backlog`,
|
|
83
|
+
`analisar_dlq`, `rastrear_mensagem_sns`).
|
|
84
|
+
|
|
85
|
+
## Segurança
|
|
86
|
+
|
|
87
|
+
O padrão é **readonly**. Toda escrita exige três coisas:
|
|
88
|
+
|
|
89
|
+
1. **Modo por conexão** (readonly ⊂ operator ⊂ admin): produção nasce readonly;
|
|
90
|
+
promover é mudança de config, nunca de runtime.
|
|
91
|
+
2. **Allowlist/denylist por glob**: destino negado não aparece nem na listagem.
|
|
92
|
+
3. **Confirmação em duas etapas**: a 1ª chamada roda em dry-run e devolve o impacto
|
|
93
|
+
(*"vai apagar 48.213 mensagens de orders.dlq"*) e um token com TTL curto,
|
|
94
|
+
vinculado ao hash dos argumentos; a execução só acontece na 2ª chamada com esse
|
|
95
|
+
token.
|
|
96
|
+
|
|
97
|
+
Além disso: payloads **truncados** e com **dados sensíveis redigidos** antes de
|
|
98
|
+
sair; toda MessageView marcada como `untrusted` (o conteúdo é dado, não instrução);
|
|
99
|
+
**auditoria** JSON lines de toda escrita (inclusive dry-runs); credenciais só por
|
|
100
|
+
ambiente, nunca em respostas, erros ou logs.
|
|
101
|
+
|
|
102
|
+
## Desenvolvimento
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
uv sync --extra dev
|
|
106
|
+
uv run pytest tests/unit tests/contract # sem Docker (fakes)
|
|
107
|
+
uv run pytest -m integration # brokers reais (Docker)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
A peça central de teste é uma **suíte de contrato** parametrizada por adapter: o
|
|
111
|
+
mesmo cenário (criar destino, publicar, stats, peek, lag, redrive, purge) roda em
|
|
112
|
+
todos. Um broker novo só entra quando passa nela.
|
|
113
|
+
|
|
114
|
+
## Fora de escopo na v1
|
|
115
|
+
|
|
116
|
+
Ser consumidor/worker de produção; gestão de usuários e ACLs dos brokers;
|
|
117
|
+
provisionamento de clusters; monitoramento contínuo e alertas; decodificação de
|
|
118
|
+
Avro/Protobuf no Kafka; replay de tópicos FIFO do SNS; transporte HTTP remoto.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Argonaut — servidor MCP de filas e tópicos.
|
|
2
|
+
|
|
3
|
+
Descobre, inspeciona e diagnostica filas e tópicos em Redis Streams, Kafka,
|
|
4
|
+
RabbitMQ, Amazon SQS e Amazon SNS. Operações de escrita existem para resolver o
|
|
5
|
+
problema encontrado, sempre atrás de modo, allowlist e confirmação em duas etapas.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Adapters de broker, com auto-descoberta.
|
|
2
|
+
|
|
3
|
+
Importa cada módulo de adapter do pacote para que o decorador
|
|
4
|
+
``@register_adapter`` registre a classe. Um módulo que falhe ao importar (por
|
|
5
|
+
exemplo, dependência opcional ausente) é pulado com aviso, sem derrubar os
|
|
6
|
+
demais.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import importlib
|
|
12
|
+
import logging
|
|
13
|
+
import pkgutil
|
|
14
|
+
|
|
15
|
+
from argonaut.adapters.base import (
|
|
16
|
+
BrokerAdapter,
|
|
17
|
+
get_adapter_class,
|
|
18
|
+
register_adapter,
|
|
19
|
+
registered_types,
|
|
20
|
+
)
|
|
21
|
+
from argonaut.config.schema import Config
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger("argonaut")
|
|
24
|
+
|
|
25
|
+
_SKIP = {"base"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _discover() -> None:
|
|
29
|
+
for info in pkgutil.iter_modules(__path__):
|
|
30
|
+
if info.name in _SKIP:
|
|
31
|
+
continue
|
|
32
|
+
try:
|
|
33
|
+
importlib.import_module(f"{__name__}.{info.name}")
|
|
34
|
+
except Exception as exc: # pragma: no cover - resiliência de import
|
|
35
|
+
logger.warning("Adapter '%s' não pôde ser carregado: %s", info.name, exc)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
_discover()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_adapters(config: Config) -> dict[str, BrokerAdapter]:
|
|
42
|
+
"""Instancia um adapter por conexão. Não conecta ao broker (conexão é preguiçosa)."""
|
|
43
|
+
adapters: dict[str, BrokerAdapter] = {}
|
|
44
|
+
for cid, conn in config.connections.items():
|
|
45
|
+
cls = get_adapter_class(conn.type)
|
|
46
|
+
if cls is None:
|
|
47
|
+
logger.warning(
|
|
48
|
+
"Conexão '%s': nenhum adapter registrado para o tipo '%s' "
|
|
49
|
+
"(dependência do broker instalada?).",
|
|
50
|
+
cid,
|
|
51
|
+
conn.type,
|
|
52
|
+
)
|
|
53
|
+
continue
|
|
54
|
+
adapters[cid] = cls(conn)
|
|
55
|
+
return adapters
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"BrokerAdapter",
|
|
60
|
+
"get_adapter_class",
|
|
61
|
+
"register_adapter",
|
|
62
|
+
"registered_types",
|
|
63
|
+
"build_adapters",
|
|
64
|
+
]
|