Skip to main content
Glama
MHNahib

Desco MCP

by MHNahib
README.md
# Desco MCP

![Node](https://img.shields.io/badge/node-%3E%3D24%20LTS-339933?logo=node.js&logoColor=white)
![TypeScript](https://img.shields.io/badge/typescript-%5E7.0.2-3178C6?logo=typescript&logoColor=white)
![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)
![MCP SDK](https://img.shields.io/badge/MCP%20SDK-%5E1.30.0-blue)
![Version](https://img.shields.io/badge/version-0.0.1-lightgrey)

> **Disclaimer:** This is an unofficial, community-built DESCO API provided for educational and research purposes. It is not affiliated with or endorsed by DESCO and may change, become unavailable, or return inaccurate information. The API is based on publicly available open-source resources, and the author assumes no responsibility for any loss or damage resulting from its use.

`Desco MCP` is a Node.js and TypeScript Model Context Protocol (MCP) server for DESCO customer data. It exposes DESCO account lookups and billing/usage data as MCP tools and exposes them over a Streamable HTTP endpoint. The server binds a validated API key to a specific `accountNo` and `meterNo`, so callers do not need to pass those identifiers on each tool call when using the HTTP flow.

## Features

- Streamable HTTP MCP server with a protected `/:apiKey/mcp` endpoint
- API-key-based authentication using AES-256-GCM encryption
- DESCO customer lookup tools
- customer location lookup
- daily and monthly consumption queries
- balance lookup
- recharge history lookup
- recent event lookup
- local development tunnel support via ngrok and Docker Compose
- generated API key command for account-specific access

## Tech Stack

- Node.js
- TypeScript
- Hono
- @hono/node-server
- @modelcontextprotocol/sdk
- Zod
- esbuild
- tsx
- Docker Compose (for the ngrok development tunnel)

This project does not use a database. It calls the DESCO backend over HTTP using a configured base URL.

## Project Structure

```text
desco-mcp/
├── .env.example
├── .gitignore
├── docker-compose.dev.yml
├── package.json
├── pnpm-workspace.yaml
├── tsconfig.json
├── scripts/
│   └── generate-api-key.js
├── src/
│   ├── index.ts
│   ├── main.ts
│   ├── server.ts
│   ├── lib/
│   │   └── api-client.lib.ts
│   ├── mcp/
│   │   └── desco.tools.ts
│   ├── schema/
│   │   ├── desco.schema.ts
│   │   └── mcp-tool.schema.ts
│   ├── services/
│   │   └── desco.service.ts
│   ├── types/
│   │   └── desco.type.ts
│   └── utils/
│       ├── api-key.utils.ts
│       ├── desco.utils.ts
│       ├── mcp.utils.ts
│       └── masking.ts
├── README.md
└── node_modules/
```

Key implementation areas:

- `src/index.ts`: boots the server and chooses between stdio or Streamable HTTP mode
- `src/main.ts`: starts the HTTP server and validates API keys per request
- `src/server.ts`: creates an MCP server and registers DESCO tools
- `src/mcp/desco.tools.ts`: defines and registers the available MCP tools
- `src/services/desco.service.ts`: makes HTTP requests to the DESCO backend
- `src/utils/api-key.utils.ts`: generates and validates opaque API keys
- `scripts/generate-api-key.js`: CLI helper for creating an API key from an account number and meter number

## Prerequisites

Before running the project, make sure you have:

- Node.js and npm installed
- A valid DESCO base URL in the environment configuration
- An `API_KEY_SECRET` value for key generation and validation
- Access to a DESCO account number and meter number for generating API keys
- Docker Compose installed only if you want to run the ngrok tunnel in `docker-compose.dev.yml`

Node.js `>=24` is required, pinned via the `engines` field in `package.json`.

## Installation

```bash
git clone <repository-url>
cd desco-mcp
cp .env.example .env
npm install
```

Then fill in the required values in `.env` before starting the server.

## Environment Variables

The project reads environment variables via `--env-file=.env` in the scripts. The template is defined in `.env.example`.

```env
API_KEY_SECRET=
PORT=3000
DESCO_BASE_URL=
NGROK_AUTHTOKEN=
```

| Variable          | Required | Description                                                                                                              |
| ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| `API_KEY_SECRET`  | Yes      | Secret used to derive the AES-256-GCM key used to generate and validate API keys. Changing it invalidates existing keys. |
| `PORT`            | No       | HTTP port for the Streamable HTTP server. Defaults to `3000`.                                                            |
| `DESCO_BASE_URL`  | Yes      | Base URL of the DESCO API backend that the server calls.                                                                 |
| `NGROK_AUTHTOKEN` | No       | Ngrok authentication token used only by the local development tunnel in `docker-compose.dev.yml`.                        |

## Running the Project

### Start the app in development mode

```bash
npm run dev
```

This starts the server using the `.env` file and watches for source changes.

### Build the app

```bash
npm run build
```

This runs TypeScript validation with `tsc --noEmit` and bundles the project into `dist/index.js` using esbuild.

### Start the built app

```bash
npm start
```

This launches the compiled server from `dist/index.js` using the `.env` file.

### Inspect the MCP server

```bash
npm run mcp:inspector
```

This runs the official MCP inspector for debugging and testing the server.

### Generate an API key

```bash
npm run generate-api-key -- <accountNo> <meterNo>
```

Example:

```bash
npm run generate-api-key -- 123456789 987654321
```

This prints a generated opaque API key that can be used in the `/:apiKey/mcp` route.

## Available Scripts

| Script             | Command                                                                                                                  | Purpose                                                             |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
| `dev`              | `cross-env NODE_OPTIONS=--use-system-ca tsx watch --env-file=.env src/index.ts`                                          | Starts the server in watch mode for local development.              |
| `build`            | `tsc --noEmit && esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --packages=external` | Validates types and produces a bundled Node build.                  |
| `start`            | `cross-env NODE_OPTIONS=--use-system-ca node --env-file=.env dist/index.js`                                              | Starts the compiled production build.                               |
| `mcp:inspector`    | `npx @modelcontextprotocol/inspector@latest`                                                                             | Launches the MCP inspector for testing the server.                  |
| `generate-api-key` | `cross-env NODE_OPTIONS=--use-system-ca tsx --env-file=.env scripts/generate-api-key.js`                                 | Generates a DESCO API key from a provided account and meter number. |

## API Documentation

This project does not expose a traditional REST API in the same way as a normal web service. Instead, it exposes an MCP server over a Streamable HTTP endpoint and registers DESCO tools with the Model Context Protocol.

### Base URL

When running locally:

```text
http://localhost:3000
```

### Endpoint

```text
GET /
POST /:apiKey/mcp
GET /:apiKey/mcp
DELETE /:apiKey/mcp
OPTIONS /:apiKey/mcp
```

The project registers a CORS configuration and accepts MCP requests on `/:apiKey/mcp`.

### Authentication

The route uses a path-segment API key:

```text
http://localhost:3000/<API_KEY>/mcp
```

The key is validated with `validateApiKey()`. If the key is malformed, expired, tampered with, or generated using a different `API_KEY_SECRET`, the server returns a JSON-RPC error with HTTP status 401.

### MCP Tools

The server registers the following tools:

| Tool name                | Description                                                  | Inputs                                         |
| ------------------------ | ------------------------------------------------------------ | ---------------------------------------------- |
| `getBalance`             | Fetch prepaid balance for an account and meter.              | `accountNo`, `meterNo`                         |
| `getRecentEvent`         | Fetch the most recent event for an account.                  | `accountNo`                                    |
| `getCustomerInfo`        | Fetch DESCO customer information for an account and meter.   | `accountNo`, `meterNo`                         |
| `getRechargeHistory`     | Fetch recharge history for a date range.                     | `accountNo`, `meterNo`, `dateFrom`, `dateTo`   |
| `getCustomerLocation`    | Fetch customer location data for an account.                 | `accountNo`                                    |
| `getDailyConsumption`    | Fetch daily consumption values for a date range.             | `accountNo`, `meterNo`, `dateFrom`, `dateTo`   |
| `getMonthlyConsumption`  | Fetch monthly consumption values for a month range.          | `accountNo`, `meterNo`, `monthFrom`, `monthTo` |
| `getUnifiedCustomerInfo` | Fetch unified customer information for an account and meter. | `accountNo`, `meterNo`                         |

### Example tool call pattern

The exact request format is the MCP protocol request payload sent to the HTTP endpoint, not a custom JSON REST body defined in this repository.

```http
POST /<API_KEY>/mcp HTTP/1.1
Content-Type: application/json
```

The server then responds with standard MCP JSON-RPC-style content.

## Database

No database is configured or used by this project.

The application instead queries a remote DESCO API endpoint using a configured `DESCO_BASE_URL`. All customer data is retrieved through those HTTP requests; it is not stored locally.

## Authentication & Authorization

Authentication is implemented through a generated opaque API key rather than a session system or bearer token flow.

Flow:

1. `API_KEY_SECRET` is used to derive a SHA-256 key.
2. `generateApiKey(accountNo, meterNo)` encrypts `{ accountNo, meterNo }` with AES-256-GCM.
3. The resulting base64url string is passed as the route segment in `/:apiKey/mcp`.
4. `validateApiKey(apiKey)` decrypts and validates the payload.
5. The resolved `accountNo` and `meterNo` are bound to the request before tool execution.

If the secret changes, all previously generated keys become invalid.

## Configuration

Important configuration files:

- `.env`: runtime secrets and app settings loaded during local execution
- `.env.example`: template for required variables
- `docker-compose.dev.yml`: ngrok tunnel for exposing the local HTTP server to the internet during development
- `tsconfig.json`: TypeScript compiler configuration
- `package.json`: scripts and dependency configuration

## Testing

This repository currently does not include a dedicated test framework, test script, or test directory.

The only project verification command present is:

```bash
npm run build
```

This validates TypeScript and ensures the project bundles correctly.

## Code Quality

The project performs TypeScript checking as part of the build:

```bash
npm run build
```

There are no separate lint, formatting, or pre-commit configuration files in the repository at the moment.

## Deployment

The application is designed to run as a Node.js service.

### Local deployment

```bash
npm install
npm run build
npm start
```

### Public development exposure

A Docker Compose file is included for exposing the local app via ngrok:

```bash
docker compose -f docker-compose.dev.yml up -d
```

This uses `NGROK_AUTHTOKEN` from `.env` and forwards traffic to `host.docker.internal:3000`.

## Docker

The repository contains `docker-compose.dev.yml` but no Dockerfile for the application itself.

### Requirements

- Docker Engine
- Docker Compose
- A valid `NGROK_AUTHTOKEN` in `.env`

### Start the tunnel

```bash
docker compose -f docker-compose.dev.yml up -d
```

### Stop the tunnel

```bash
docker compose -f docker-compose.dev.yml down
```

## Troubleshooting

### `API_KEY_SECRET environment variable is not set`

Add a value to `API_KEY_SECRET` in `.env` before starting the server.

### `DESCO_BASE_URL environment variable is not set`

Set `DESCO_BASE_URL` to the correct DESCO backend base URL.

### Invalid or expired API key

This usually means:

- the key was generated with a different `API_KEY_SECRET`
- the key payload is malformed
- the key has been altered

Generate a new key with:

```bash
npm run generate-api-key -- <accountNo> <meterNo>
```

### Ngrok tunnel is not working

Verify that:

- `NGROK_AUTHTOKEN` is set in `.env`
- Docker Compose is running correctly
- the app is listening locally on port `3000`

### Server not reachable on the expected route

The server is started on:

```text
http://localhost:3000/{YOUR_API_KEY}/mcp
```

The root route `/` only returns a welcome message and version metadata.

## Security

- Keep `.env` files out of version control.
- Treat `API_KEY_SECRET` as a private secret; rotating it invalidates all prior API keys.
- The app validates API keys before processing requests.
- CORS is enabled with `origin: "*"`, which is permissive and should be reviewed before production use.
- The code validates DESCO API payloads with Zod schemas before returning values to callers.

5. Keep route and schema updates consistent with the actual DESCO API contract and the implementation under `src/`.

## License

This project is licensed under the [MIT License](LICENSE).