mcp_cobol
by nba67000
README.md
# mcp_cobol — comprendre une base COBOL legacy avec un LLM
Un serveur **MCP** (Model Context Protocol) qui donne a un LLM les moyens
d'explorer une base COBOL mainframe qu'il n'a jamais vue : graphe d'appels,
usage des copybooks, tables DB2, analyse d'impact.
Le LLM de la demo est **Mistral** (La Plateforme). Le serveur, lui, est
agnostique : n'importe quel client MCP peut le consommer.
> Base de demonstration : [AWS CardDemo](https://github.com/aws-samples/aws-mainframe-modernization-carddemo),
> une application CICS / DB2 / VSAM de gestion de cartes de credit publiee par
> AWS. Aucun code client n'est utilise.
## Le probleme
Une base COBOL de 20 ans, c'est quelques centaines de programmes, des
copybooks partages par tout le monde, des `XCTL` CICS qui sautent de programme
en programme, et personne qui sait plus repondre a *« qu'est-ce qui casse si je
rallonge ce champ ? »*.
Un LLM seul ne peut pas repondre : la base ne tient pas dans son contexte, et
il ne l'a jamais vue. Donner au LLM des **outils** pour interroger la base —
plutot que d'esperer qu'il la memorise — c'est exactement ce que resout MCP.
## Architecture
```
vendor/carddemo parser/ data/index.json
(COBOL brut) ──▶ cobol_parser.py ──▶ faits extraits
build_index.py + index inverse
│
▼
demo/ask.py ◀── mistral/client.py ◀── server/mcp_server.py
(question NL) (function calling) (outils MCP)
```
Trois etages, volontairement decouples :
1. **Parsing** — lent, heuristique, sans reseau. Produit un JSON.
2. **Serveur MCP** — lit le JSON, expose 5 outils. Zero dependance LLM.
3. **Client LLM** — Mistral decide *quels* outils appeler et dans quel ordre.
Le serveur ne relit jamais le COBOL : chaque appel d'outil est quasi
instantane, ce qui rend la demo fluide en live.
## Outils MCP
| Outil | Question a laquelle il repond |
|---|---|
| `list_programs` | Qu'est-ce qu'il y a dans cette base ? |
| `explain_program` | Que fait ce programme, avec quoi travaille-t-il ? |
| `call_graph` | Qui appelle qui ? |
| `who_uses_copybook` | Qui depend de cette structure de donnees ? |
| `impact_analysis` | Qu'est-ce qui casse si je modifie ca ? |
## Installation
Prerequis : Python ≥ 3.10, git, et une cle API Mistral
([console.mistral.ai](https://console.mistral.ai/api-keys)).
```bash
bash setup.sh # venv + dependances + clone de CardDemo + .env
```
Puis renseigne ta cle dans `.env` :
```ini
MISTRAL_API_KEY=...
```
`.env` est gitignore — la cle ne quitte jamais ta machine.
## Utilisation
```bash
# 1. Indexer la base COBOL (a refaire si le code source change)
.venv/Scripts/python.exe -m parser.build_index
# 2. Poser une question en langage naturel
.venv/Scripts/python.exe demo/ask.py "Que fait le programme COACTVWC ?"
```
*(Sous Linux/macOS : `.venv/bin/python` au lieu de `.venv/Scripts/python.exe`.)*
### Questions de demonstration
```bash
demo/ask.py "Que fait le programme COACTVWC et quelles tables DB2 utilise-t-il ?"
demo/ask.py "Qu'est-ce qui casse si je modifie le copybook CVACT01Y ?"
demo/ask.py "Montre-moi la chaine d'appels depuis le menu principal."
```
## Etat d'avancement
- [x] **Etape 1** — fondation : arborescence, setup, gestion des secrets
- [x] **Etape 2** — parseur COBOL fixed-format + index inverse
- [x] **Etape 2b** — graphe de flot interne + outil `explain_program`
- [x] **Etape 3** — serveur MCP : 5 outils, autotest 32/32
- [x] **Etape 4** — integration Mistral : wrapper + pont de function calling
- [ ] **Etape 5** — demo de bout en bout + doc
La chaine complete est validee de bout en bout avec `mistral-medium-latest` :
trois questions, dont une resolue en 5 appels d'outils sur 6 tours.
> Si `python -m demo.ask` repond `429` avec
> `x-ratelimit-limit-req-minute: 0`, ce n'est pas une limitation passagere :
> le compte n'a aucun droit d'appel sur les completions (alors que
> `/v1/models` repond 200 — lister et appeler relevent de droits differents).
> `python -m mistral.client` le diagnostique en une seconde. En attendant,
> `--simulate` exerce tout le pont sans API.
## Ce que le parseur extrait
Sur les 44 programmes de CardDemo (22 893 lignes de code) :
| | |
|---|---|
| Programmes | 25 transactionnels CICS, 17 batch, 2 sous-programmes |
| Copybooks | 62 presents + 8 externes identifies (MQ, CICS systeme) |
| Paragraphes | 870, relies par 1 163 `PERFORM` et 185 `GO TO` |
| Graphe d'appels | 92 aretes |
| Donnees | 8 fichiers VSAM, 2 tables DB2, 2 segments IMS |
| Ecrans | transaction CICS et map BMS resolues par programme |
### Le point dur : les appels indirects
CardDemo n'ecrit jamais le nom du programme cible dans un `XCTL` :
```cobol
MOVE LIT-MENUPGM TO CDEMO-TO-PROGRAM *> LIT-MENUPGM VALUE 'COMEN01C'
EXEC CICS XCTL PROGRAM(CDEMO-TO-PROGRAM) END-EXEC
```
Un `grep XCTL` ne remonte donc **aucun** appel exploitable. Le parseur remonte
la chaine (clauses `VALUE`, `MOVE` successifs, tables `OCCURS` declarees dans
les copybooks) et **etiquette chaque arete** par sa methode de resolution :
| Methode | Aretes | Confiance |
|---|---|---|
| nom litteral dans l'instruction | 64 | certain |
| clause `VALUE` / `MOVE` de litteral | 17 | certain |
| chaine de deux `MOVE` | 11 | probable |
| table de programmes d'un copybook | 3 | heuristique |
Aucune arete ne reste non resolue. Et surtout : une arete heuristique est
**presentee comme telle**, avec sa provenance (« table declaree dans
COMEN02Y »). Un outil d'analyse d'impact qui affirme sans nuancer est pire
qu'inutile.
### Essayer un outil sans serveur ni cle API
La logique des outils (`server/tools.py`) est separee de la plomberie MCP.
On peut donc interroger l'index directement :
```bash
.venv/Scripts/python.exe -m server.tools list --role batch
.venv/Scripts/python.exe -m server.tools explain COACTVWC
.venv/Scripts/python.exe -m server.tools graph COMEN01C --depth 2
.venv/Scripts/python.exe -m server.tools copybook CVACT01Y
.venv/Scripts/python.exe -m server.tools impact CVACT01Y
```
```
# COACTVWC — Accept and process Account View request
- Role : transactionnel-cics
- Source : app/cbl/COACTVWC.cbl (703 lignes de code, 35 paragraphes)
- Transaction CICS : CAVW
- Ecran BMS : CACTVWA (mapset COACTVW)
## Donnees manipulees
- VSAM ACCTDAT — lecture (READ)
- VSAM CUSTDAT — lecture (READ)
- VSAM CXACAIX — lecture (READ)
## Navigation
- Appele par : COMEN01C
- Appelle : COMEN01C via XCTL (l. 349) — probable
## Flot interne
Point d'entree : 0000-MAIN — 17 PERFORM, 9 GO TO, 1 gestionnaire CICS
358 1000-SEND-MAP THRU 1000-SEND-MAP-EXIT
362 2000-PROCESS-INPUTS THRU 2000-PROCESS-INPUTS-EXIT
369 9000-READ-ACCT THRU 9000-READ-ACCT-EXIT
## A savoir avant de conclure
- 2 copybooks absents du depot (DFHAID, DFHBMSCA) : dependances CICS
- Ecran designe par une variable non resolue (CCARD-NEXT-MAP) : valeur
transmise par l'appelant via la COMMAREA
- 3 paragraphes cible d'aucun PERFORM/GO TO/LABEL
```
Chaque outil produit une section **« A savoir avant de conclure »** : ce qu'il
ne sait pas. C'est elle qui empeche le LLM d'affirmer ce que l'analyse
statique n'etablit pas.
## Le serveur MCP
```bash
.venv/Scripts/python.exe -m server.selftest # verifie les 5 outils
.venv/Scripts/python.exe server/mcp_server.py # lance le serveur
```
Le serveur dialogue en JSON-RPC sur stdin/stdout : lance seul, il n'affiche
rien. C'est normal. L'autotest, lui, le verifie de deux facons — branche en
memoire sur l'objet serveur, puis lance comme un **vrai sous-processus**. Le
second mode est le seul qui detecte une ecriture parasite sur stdout, laquelle
corrompt le protocole de maniere tres difficile a diagnostiquer.
### Exemple : « qu'est-ce qui casse si je modifie le copybook CVACT01Y ? »
```
# Impact d'une modification de copybook CVACT01Y
23 programme(s) concerne(s) : 14 a recompiler, 9 a retester.
Repartition : 7 batch, 16 transactionnel-cics
## A recompiler — embarquent la modification
- CBACT01C (batch) — inclut CVACT01Y directement
- COACTVWC (transactionnel-cics) — inclut CVACT01Y directement
... 12 autres
## A retester — appellent un programme modifie
- COMEN01C (transactionnel-cics) — chaine : COMEN01C -> COACTUPC
- COPAUS1C (transactionnel-cics) — chaine : COPAUS1C -> COPAUS0C
... 7 autres
## Perimetre de recette
- Transactions CICS : CAUP, CAVW, CB00, CC00, CCDL, CCLI, CCUP, CM00,
CPVD, CPVS, CR00, CT00, CT01, CT02
- Fichiers VSAM : ACCTDAT, CARDAIX, CARDDAT, CCXREF, CUSTDAT, CXACAIX,
TRANSACT, USRSEC
- Segments IMS : PAUTDTL1, PAUTSUM0
```
La separation **a recompiler** / **a retester** n'est pas cosmetique : inclure
un copybook modifie impose une recompilation, appeler un programme modifie
n'impose qu'une recette. Ce sont deux charges de travail differentes.
### Brancher un autre client MCP
Le serveur ne contient aucune reference a un modele particulier. N'importe quel
client MCP le consomme avec une configuration de cette forme :
```json
{
"mcpServers": {
"cobol-legacy": {
"command": "/chemin/vers/mcp_cobol/.venv/Scripts/python.exe",
"args": ["/chemin/vers/mcp_cobol/server/mcp_server.py"]
}
}
}
```
## L'integration Mistral
```bash
python -m mistral.client # pre-vol : config, modeles, appel test
python -m demo.ask "Que fait le programme COACTVWC ?"
python -m demo.ask --simulate "..." # le pont, sans appeler l'API
```
### Mistral ne parle pas MCP : le pont
Il n'existe aucun lien direct entre les deux protocoles. `demo/ask.py` fait
trois traductions :
| | MCP | Mistral |
|---|---|---|
| Declaration | `{name, description, input_schema}` | `{"type":"function","function":{name, description, parameters}}` |
| Appel | `mcp.call_tool(nom, args)` | `tool_calls[].function.arguments` (JSON **texte**) |
| Resultat | blocs de contenu | message `{"role":"tool", "tool_call_id", "content"}` |
Puis on boucle : tant que le modele demande des outils, on execute et on
renvoie. Le `tool_call_id` est ce qui relie une reponse a sa question quand le
modele appelle plusieurs outils d'un seul coup.
Le serveur MCP tourne dans un **vrai sous-processus**, en JSON-RPC sur
stdin/stdout. Il ignore l'existence de Mistral. Changer de fournisseur ne
touche que `demo/ask.py` et `mistral/client.py`.
### Souverainete
Le SDK expose trois endpoints : `api.mistral.ai`, **`api.eu.mistral.ai`**,
`api.us.mistral.ai`. Mettre `MISTRAL_SERVER=eu` dans `.env` route tout le
trafic par l'Union europeenne.
### Verifier le pont sans quota API
```bash
python -m demo.ask --simulate "Qu'est-ce qui casse si je modifie le copybook CVACT01Y ?"
```
```
[demo] 5 outils MCP disponibles : list_programs, explain_program, call_graph,
who_uses_copybook, impact_analysis
[tour 1] impact_analysis(target='CVACT01Y')
-> 2726 caracteres | # Impact d'une modification de copybook CVACT01Y
[demo] termine en 2 tour(s)
```
`--simulate` remplace le modele par une doublure scriptee et exerce la **meme**
boucle sur les **vrais** outils MCP : les trois traductions sont donc
reellement testees. La doublure annonce explicitement qu'aucun modele n'a
repondu — elle ne peut pas etre confondue avec une reponse du LLM.
## Limites assumees
- Analyse **statique et non sensible au flot** : la resolution est faite par
variable, pas par chemin d'execution. Si un programme charge deux valeurs
differentes dans la meme variable a deux endroits, les deux cibles sont
listees. C'est une **sur-approximation volontaire** : en analyse d'impact,
mieux vaut un candidat de trop qu'un oubli.
- Les **47 JCL ne sont pas analyses**. Un programme classe `batch` l'est parce
qu'aucun COBOL ne l'appelle, pas parce qu'on a lu sa carte `EXEC PGM=`.
- Le parseur cible le **fixed-format** (colonnes 7-72), le format historique
mainframe. Le free-format n'est pas l'objectif.
- Pas de vocation a remplacer un outil commercial d'analyse d'impact : la
valeur demontree ici est le **couplage base legacy ↔ LLM**.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues