Slack-native command center for enterprise legacy modernization teams.
Built by a modernization practitioner with 22 years across COBOL, OLTP, and mainframe legacy estates. This is the operator's view of where migration programs actually break — not in code conversion, but in the coordination of business rules, dependencies, and SME validation that conversion silently assumes is already done.
Legacy Modernization Commander turns a legacy module into a business-readable modernization assessment inside Slack: what the module does, which business rules it encodes, where migration risk concentrates, what SMEs must validate, and which work packages should move next. Built for the Slack Agent Builder Challenge as a focused, portfolio-grade agentic workflow.
Hackathon technology requirement: MCP server integration. Legacy analysis is exposed as an MCP server (src/mcp/server.ts) over stdio; the Slack app consumes it as an MCP client (src/domain/mcp-legacy-analysis-client.ts). Details below.
/legacy assess claims-batch
Returns a structured modernization assessment for a synthetic COBOL claims-batch module (CLAIMS-BATCH, z/OS batch, insurance claims adjudication).
The command runs in one of three modes:
| Mode | Invocation | Behavior |
|---|---|---|
| Auto | /legacy assess claims-batch |
Agent if ANTHROPIC_API_KEY is set, otherwise fixture. |
| Agent | /legacy assess claims-batch --agent |
Live Claude grounding (claude-sonnet-4-6) against real COBOL source, via MCP. |
| Fixture | /legacy assess claims-batch --fixture |
Deterministic local fixture. No model call. |
Auto mode means the demo always works — with a key it grounds live; without one it falls back to a deterministic assessment of the same shape.
Legacy analysis is not embedded in the Slack app. It is a standalone MCP server that the Slack app talks to as a client over stdio.
src/mcp/server.ts MCP server — McpServer + StdioServerTransport
src/mcp/tools.ts Tool implementations exposed over MCP
src/domain/mcp-legacy-analysis-client.ts
MCP client — Client + StdioClientTransport,
implements LegacyAnalysisClient
Run the server standalone:
npm run mcp:server
The server (legacy-modernization-commander-mcp) registers three tools:
| Tool | Purpose |
|---|---|
legacy.assess_module |
Resolve a legacy module and return business purpose, modernization risk, dependencies, and SME questions. |
legacy.extract_rules |
Extract candidate business rules from a legacy module assessment source. |
legacy.create_plan |
Create migration path steps and ticket-draft work packages for a legacy module. |
Each tool takes a moduleId and returns structured JSON. These three map directly onto the LegacyAnalysisClient interface the Slack app depends on — MCP is the transport, not a bolt-on.
This matters architecturally, not just for compliance: the Slack app depends on the LegacyAnalysisClient interface, and MCP is one implementation of it. A production deployment can point the same client at a real code-analysis backend without touching the Slack workflow layer.
This is the point of the project.
The agent proposes a grounded assessment. The deterministic application layer owns SME validation state.
Every claim the agent produces — risk level, each business rule, each work package — is stamped machine_inferred or sme_required. The model has no path to sme_validated. That status can only be reached through the application's SME validation workflow, and the type system enforces it: the agent's output type cannot express a validated claim. This is covered by adversarial tests that assert the model can never emit or escalate to a validated state.
The App Home dashboard reflects validated workflow state, not raw model output. Model-proposes / app-validates is enforced at the type level, not by convention.
- The model is given the real source (
src/demo/source/claims-batch.cbl) and proposes citations — paragraph and line references — for each rule and risk it asserts. verifyAndStamp(pure, no model in the loop) resolves each proposed reference against the actual source lines.- Resolved references mint catalog evidence (
EV-###) in theEvidenceCatalog, each carrying a file/paragraph/line locator and the real excerpt. - Each claim is assigned a
validationStatus. A claim whose citation does not resolve does not get to stand as grounded.
Every assertion in the assessment traces to a row in the evidence catalog. No catalog reference, no claim.
The model never emits a validation checklist. The application derives the smeValidationChecklist from the model's unknowns. This keeps the agent in its lane (surfacing what it doesn't know) and the application in its lane (owning the workflow that resolves it).
The assessment is not a one-shot report. It is the entry point to a workflow.
The assessment card carries action buttons:
- Mark reviewed
- SME follow-up
- Draft ticket
- Show trace
Each SME decision updates the App Home live dashboard and posts a refreshed traceability graph (PNG) to the demo channel. The dashboard tracks validation state across the assessment as SMEs work through it — the workflow surface, not a static document.
Honest framing is a hard rule in this repo. Every claim maps to a visible artifact.
- Real: the MCP server and client, the agent grounding loop, citation verification against actual source lines, the evidence catalog, the type-enforced validation boundary, the interactive SME workflow, the traceability graph.
- Synthetic and clearly labeled: the
CLAIMS-BATCHmodule and its source are a representative synthetic COBOL artifact, not customer code. - Not claimed: production-grade COBOL parsing, live enterprise-system integration, or automatic ticket creation. Work packages are drafts for human review — nothing is filed in Jira.
Outputs are grounded, traceable, and reviewable. They are an input to SME validation, not a substitute for it.
flowchart TB
subgraph slack["Slack surface — src/app/"]
cmd["/legacy assess claims-batch<br/>· auto · --agent · --fixture"]
card["Assessment card + action buttons<br/>Mark reviewed · SME follow-up · Draft ticket · Show trace"]
home["App Home dashboard<br/>+ traceability graph PNG → demo channel"]
end
subgraph domain["Orchestration — src/domain/"]
orch["orchestrator.ts"]
factory["client-factory.ts<br/>auto = agent if ANTHROPIC_API_KEY, else fixture"]
fixture["fixture client<br/>deterministic prebuilt assessment"]
end
subgraph proposes["MODEL PROPOSES (untrusted) — over MCP"]
mcpclient["mcp-legacy-analysis-client.ts<br/>MCP client · stdio"]
mcpserver["src/mcp/server.ts<br/>legacy.assess_module · legacy.extract_rules · legacy.create_plan"]
agent["claude-sonnet-4-6 grounding loop"]
cbl[("src/demo/source/claims-batch.cbl<br/>synthetic COBOL source")]
end
subgraph validates["APP VALIDATES (deterministic, pure)"]
ground["grounding.ts · verifyAndStamp<br/>resolve citations → mint EV-### → stamp status"]
catalog[["EvidenceCatalog<br/>every claim ↔ EV-### ↔ real line"]]
decision["validation-decision.ts<br/>SME workflow — only path to sme_validated"]
end
cmd --> orch --> factory
factory -->|agent mode| mcpclient
factory -->|fixture mode| fixture
mcpclient <-->|MCP stdio| mcpserver
mcpserver --> agent
cbl --- agent
agent -->|proposed citations| ground
ground --> catalog
ground -->|machine_inferred / sme_required| card
fixture --> card
card -->|SME decision| decision
decision -->|sme_validated / rejected| home
catalog -.->|trace refs| home
style proposes fill:#fff4e5,stroke:#b26a00,color:#5c3d00
style validates fill:#e8f0fe,stroke:#1a73e8,color:#0b3d91
style cbl fill:#fef7e0,stroke:#b26a00
style decision fill:#d2e3fc,stroke:#1a73e8
The LegacyAnalysisClient boundary lets a production version swap the MCP-backed agent for a real code-analysis backend, dependency mapper, or ticketing system without touching the Slack workflow layer.
Install:
npm install
Required environment variables (.env, never committed):
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=...
SLACK_APP_TOKEN=xapp-...
PORT=3000
NODE_ENV=development
Optional — enables agent mode:
ANTHROPIC_API_KEY=sk-ant-...
Deterministic local demo (no Slack, no key):
npm run demo
Slack app (Socket Mode):
npm run slack:dev
MCP server standalone:
npm run mcp:server
Slash command setup, scopes, and Socket Mode configuration: see docs/SLACK_SETUP.md.
npm test
Type check only:
npm run typecheck
Full CI gate (typecheck + tests):
npm run ci
CI runs tsc --noEmit before the test suite. Type-stripping at runtime can let tests pass while TypeScript is broken, so the type check is a required gate, not an afterthought.
docs/ Product, architecture, demo, and submission notes
slack/ Slack app manifest
src/app/ Slack entry points, rendering, App Home, action handlers
src/mcp/ MCP server and tool implementations
src/domain/ Assessment types, orchestration, MCP + fixture clients
src/demo/ Synthetic source and deterministic fixtures
tests/ Unit, behavior, and adversarial boundary tests
MIT