Skip to main content
Glama

WiSense-MCP

WiSense-MCP is a reproducible WiFi Channel State Information (CSI) sensing project for multi-user presence detection and activity recognition. The project uses the public WiMANS dataset, trains a temporal deep-learning model from CSI amplitude measurements, combines several independently trained models with a soft ensemble, and exposes the finished system through both a browser dashboard and a Model Context Protocol (MCP) server. The dashboard can analyze samples already in WiMANS or run inference on a compatible CSI capture uploaded from another sensor.

The repository is organized as a complete pipeline rather than a collection of isolated notebooks. A new user should be able to place the dataset in the configured directory, run the scripts in order, reproduce the preprocessing and training workflow, evaluate the final ensemble, and then start the dashboard and MCP endpoint without editing the Python source code. Once the model is available, a user can also upload a compatible CSI file through the browser and obtain a prediction without adding that sample to the project dataset.

What the system does

Each WiMANS sample contains approximately three seconds of WiFi CSI. The model receives the CSI amplitude from 270 channels and predicts two things for each of six anonymized user slots:

  • whether the user is present;

  • which of nine activities the user is performing when present.

The nine activity classes are jump, lie_down, nothing, pick_up, rotation, sit_down, stand_up, walk, and wave.

The final model uses a temporal CNN followed by a packed bidirectional LSTM. Instead of reducing the entire sequence to a single recurrent state, it uses temporal-pyramid attention to retain information from the full sequence as well as its early, middle, and late portions. Three independently trained models are then combined by averaging their output probabilities. The same inference path is used for cached WiMANS samples and compatible CSI files uploaded through the dashboard.

CSI amplitude: 270 channels x up to 3000 packets
                     |
                     v
              Temporal CNN
                     |
                     v
          Packed bidirectional LSTM
                     |
          +----------+----------+----------+
          |          |          |          |
        Global     Early      Middle      Late
       attention  attention   attention   attention
          |          |          |          |
          +----------+----------+----------+
                     |
                     v
             Feature fusion
                     |
          +----------+----------+
          |                     |
      Presence head          Activity head
       6 sigmoid             6 x 9 softmax
          |                     |
          +----------+----------+
                     |
                     v
          Equal-weight seed ensemble

Related MCP server: Fusion MCP Server

Dataset

This project uses WiMANS: A Benchmark Dataset for WiFi-based Multi-user Activity Sensing, introduced at ECCV 2024. WiMANS contains 11,286 three-second samples collected using 2.4 GHz and 5 GHz WiFi, with synchronized video reference data and annotations for multiple simultaneous users.

Official resources:

The dataset itself is not redistributed in this repository. Download it separately and follow the WiMANS dataset terms.

Only annotation.csv and the supplied CSI amplitude .npy files are required by the training pipeline in this repository. Raw .mat CSI files and synchronized videos are not required unless you want to perform additional analyses outside the provided pipeline.

Place the data in the following structure by default:

data/
└── raw/
    └── WiMANS/
        ├── annotation.csv
        └── wifi_csi/
            └── amp/
                ├── act_1_1.npy
                ├── act_1_2.npy
                └── ...

If you prefer a different location, change dataset_dir in config.ini. No Python file needs to be edited.

Reference results

The current reference run used a stratified 70/10/20 train/validation/test split and a three-model equal-weight ensemble. The final test split was opened only after the model configuration, checkpoints, presence threshold, and ensemble rule had been frozen.

Metric

Final test result

Presence Macro F1

0.94495

Presence Precision

0.93468

Presence Recall

0.95568

Activity Top-1 Accuracy

0.40442

Activity Macro F1

0.40455

Structured Macro F1

0.44136

Structured Accuracy

0.71420

Absent-user false-positive rate

0.04869

The structured metric treats each user slot as a ten-state prediction problem: one absent state plus the nine activity states.

These results should not be treated as a direct leaderboard comparison with the benchmark numbers reported in the WiMANS paper. This repository uses its own split and structured multi-user evaluation protocol.

Repository layout

.
├── config.ini
├── requirements.txt
├── artifacts/
│   └── best_values.json
├── scripts/
│   ├── build_splits.py
│   ├── compute_normalization.py
│   ├── build_cache.py
│   ├── train.py
│   ├── build_ensemble.py
│   ├── evaluate_test.py
│   ├── verify_inference.py
│   └── run_server.py
└── src/
    ├── settings.py
    ├── ml/
    │   ├── dataset.py
    │   ├── factory.py
    │   ├── labels.py
    │   ├── metrics.py
    │   ├── model.py
    │   └── training.py
    └── wisense_mcp/
        ├── diagnostics.py
        ├── inference.py
        ├── mcp_server.py
        ├── repository.py
        ├── sample_catalog.py
        ├── web_app.py
        └── static/
            ├── index.html
            ├── app.css
            └── app.js

config.ini contains the user-editable project settings. artifacts/best_values.json contains the selected hyperparameters from the model-selection stage used to reproduce the reference model. Training reads this file automatically; the selected values are not duplicated in the training source code.

Installation

Python 3.10 or newer is recommended. A CUDA-capable GPU is strongly recommended for training, although the code can fall back to CPU execution.

Clone the repository and enter the project directory:

git clone https://github.com/abhinz16/WiSense.git
cd WiSense

Create and activate a virtual environment:

python -m venv .venv
source .venv/bin/activate

On Windows PowerShell, activate it with:

.venv\Scripts\Activate.ps1

Install the dependencies:

python -m pip install --upgrade pip
pip install -r requirements.txt

If you want GPU acceleration, make sure your PyTorch installation is compatible with your NVIDIA driver and CUDA environment. The PyTorch installation selector at https://pytorch.org/get-started/locally/ is the safest way to choose the appropriate build.

Configuration

The normal workflow should not require editing any Python code. User settings are collected in config.ini.

The most commonly changed entries are:

[paths]
dataset_dir = data/raw/WiMANS

[training]
seeds = 42, 43, 44
max_epochs = 200
num_workers = 8
use_amp = true

[server]
host = 127.0.0.1
port = 8000
external_sample_max_mb = 50
external_sample_formats = .npy, .npz, .csv, .txt

The default split is 70% training, 10% validation, and 20% test. Global per-channel normalization statistics are computed from the training split only.

The tracked artifacts/best_values.json file is not intended as a user-input file for normal reproduction. It records the selected hyperparameters used by the reference model, and scripts/train.py reads it automatically. Editing it defines a different model configuration.

Running the full pipeline

Run the scripts from the repository root and in the order shown below.

1. Build the train, validation, and test splits

python scripts/build_splits.py

This reads annotation.csv, creates the multi-user labels, stratifies by environment, WiFi band, and simultaneous user count, and writes the split manifests to the directory configured as split_dir.

Default outputs:

data/processed/splits/
├── manifest.csv
├── train.csv
├── validation.csv
├── test.csv
└── split_summary.json

2. Compute normalization statistics

python scripts/compute_normalization.py

The channel means and standard deviations are calculated using the training split only. This prevents validation or test information from entering preprocessing.

Default output:

data/processed/normalization/training_channel_stats.npz

3. Build the tensor cache

python scripts/build_cache.py

Each CSI amplitude sequence is normalized with the training statistics, padded or truncated to the configured target length, and stored in memory-mappable NumPy arrays. True packet lengths are saved separately so the recurrent model can ignore padded timesteps.

Default outputs include:

data/processed/tensor_cache/
├── train_x.npy
├── train_y.npy
├── train_lengths.npy
├── validation_x.npy
├── validation_y.npy
├── validation_lengths.npy
├── test_x.npy
├── test_y.npy
├── test_lengths.npy
└── cache_config.json

With the full WiMANS dataset and the default float16 cache, the input tensors require roughly 17 GiB of disk space, so check available storage before running this step.

4. Train the seeded models

python scripts/train.py

The script reads the seeds and training controls from config.ini and the selected model hyperparameters from artifacts/best_values.json. Each seed is trained independently. The best checkpoint for each seed is selected using validation activity Macro F1, with early stopping and a learning-rate scheduler.

Default checkpoints:

models/
├── model_seed_42.pt
├── model_seed_43.pt
└── model_seed_44.pt

Training histories and summary metrics are written under results/training/.

5. Build the validation ensemble

python scripts/build_ensemble.py

This runs the trained checkpoints on the validation split and averages their presence and activity probabilities with equal weight. It does not retrain the models or optimize ensemble weights.

Default outputs are written to:

results/ensemble/

The saved validation probabilities are later used to verify that the production inference path reproduces the same model behavior.

6. Run the locked final test evaluation

python scripts/evaluate_test.py

This is intentionally a separate step. Before reading the test data, the script writes a model-lock file containing the checkpoint paths, SHA-256 hashes, seeds, ensemble rule, and decision threshold. It then performs the final test evaluation and saves the reporting artifacts.

Default outputs:

results/final/
├── model_lock.json
├── metrics.json
├── report.json
├── individual_metrics.csv
├── per_class_metrics.csv
├── confusion_matrix.csv
└── predictions.npz

By default, prevent_test_overwrite = true in config.ini. If final metrics already exist, the script stops instead of silently reevaluating the test set. A deliberate full rerun requires removing the previous final-results directory first.

7. Verify production inference

python scripts/verify_inference.py

This compares live inference with the probabilities saved during the validation and final test evaluations. The number of checked samples and numerical tolerance are controlled by config.ini.

A successful run ends with output similar to:

Verified 6/6 samples

Small floating-point differences are expected when batched evaluation and single-sample GPU inference use different CUDA kernels. The default tolerance is 0.001, while the presence decisions and activity argmax values are also checked for agreement.

Starting the dashboard and MCP server

The browser interface and MCP server are hosted by the same Starlette/Uvicorn application.

Start it with:

python scripts/run_server.py

With the default configuration, the following endpoints become available:

Dashboard:     http://127.0.0.1:8000/
Health check:  http://127.0.0.1:8000/api/health
MCP endpoint:  http://127.0.0.1:8000/mcp

The dashboard provides sample browsing, live model inference, final-test analytics, error inspection, and CSI diagnostics. In Live Analyze, the input source can be switched between a sample already in the WiMANS dataset and a compatible CSI file collected from another sensor. The neural-network ensemble is loaded lazily, so opening the dashboard does not immediately allocate the models on the GPU.

Running inference on a new sensor sample

The New Sensor Sample option in the dashboard is intended for testing CSI collected outside the WiMANS dataset. The uploaded file is read directly for inference and is not added to the dataset or written into the project results. Ground-truth labels are not required.

Input requirements

The trained network expects 270 CSI channels per packet, matching the channel meaning and antenna/subcarrier ordering used during training. With the WiMANS representation, those 270 values correspond to the flattened 3 x 3 x 30 CSI dimensions for each packet.

The uploader accepts the following file types by default:

Format

Expected contents

.npy

One NumPy array containing real amplitude values or complex CSI

.npz

A NumPy archive containing a numeric CSI array

.csv

UTF-8 numeric CSI data, comma separated

.txt

UTF-8 numeric CSI data, whitespace separated

The allowed extensions and maximum upload size are controlled by config.ini:

[server]
external_sample_max_mb = 50
external_sample_formats = .npy, .npz, .csv, .txt

For .npz files, the loader first looks for arrays named csi, amplitude, amp, data, or sample. If none of those keys are present, it uses the first numeric array in the archive.

Supported array layouts

The simplest and recommended layout is one row per packet:

[packet_count, 270]

For example, a three-second capture with 3000 packets can be saved as:

import numpy as np

# csi_amplitude has shape (3000, 270)
np.save("new_sensor_sample.npy", csi_amplitude)

The loader also accepts:

[270, packet_count]
[packet_count, 3, 3, 30]
[3, 3, 30, packet_count]

More generally, a multidimensional array is accepted when exactly one axis can be interpreted as the packet axis and the product of all remaining dimensions is 270. A flat one-dimensional array is also accepted when its total length is an exact multiple of 270.

Complex-valued CSI is converted to amplitude with abs(CSI) before normalization. Real-valued input is treated as CSI amplitude. Non-finite values are replaced using the training-set channel means. The sample is then normalized with the same training-only per-channel mean and standard deviation used by the model.

The default model input length is 3000 packets. Shorter captures are zero-padded after normalization, while longer captures are truncated to the configured target length. The valid packet length is still passed to the recurrent model so padded timesteps are excluded from sequence processing.

Uploading a sample from the dashboard

Start the application normally:

python scripts/run_server.py

Open:

http://127.0.0.1:8000/

Then open Live Analyze, select New Sensor Sample, and either drag the CSI file onto the upload area or click Choose file. After the file is selected, click Run Neural Inference.

The result view shows the detected users, presence probabilities, predicted activity for each present user, top activity probabilities, inference latency, device information, the detected input layout, packet counts, and a compact CSI-energy preview. Because an uploaded sensor sample has no project annotation, the dashboard reports predictions only and does not calculate ground-truth correctness for that sample.

Sensor compatibility

A file being accepted by the uploader does not guarantee that its predictions are calibrated. The model was trained on the WiMANS CSI representation, so a new sensor should preserve the same 270-channel interpretation and ordering as closely as possible. Changes in WiFi hardware, antenna layout, subcarrier selection, CSI extraction method, sampling behavior, environment, or signal scaling can introduce domain shift.

If a sensor produces a different number of CSI channels, the current model cannot use that sample directly. If it produces 270 channels with a different physical meaning or ordering, the code may run but the resulting prediction should not be treated as reliable without validation, domain adaptation, or retraining.

The browser upload workflow is currently separate from the MCP sample tools. MCP tools operate on samples already available through the project dataset/cache, while external sensor files are uploaded through the dashboard.

Using the MCP server

The MCP endpoint uses Streamable HTTP and is mounted at /mcp. Any MCP client that supports Streamable HTTP can connect to:

http://127.0.0.1:8000/mcp

Keep scripts/run_server.py running while the MCP client is connected.

Testing with MCP Inspector

The easiest way to inspect the server locally is the official MCP Inspector. It requires Node.js and npx.

Start WiSense in one terminal:

python scripts/run_server.py

In another terminal, start the Inspector:

npx -y @modelcontextprotocol/inspector

Open the URL printed by the Inspector, choose the Streamable HTTP transport, and connect to:

http://127.0.0.1:8000/mcp

The Inspector will discover the tools exposed by the server and lets you call them manually.

MCP tools

The MCP interface is read-only. It exposes the following tools:

Tool

Purpose

get_dataset_summary

Dataset labels, environments, WiFi bands, and split counts

get_final_model_metrics

Locked final test metrics and model-lock metadata

get_activity_performance

Per-class precision, recall, F1, and support

get_top_activity_confusions

Largest directional activity confusions

compare_wifi_bands

Performance by 2.4/5 GHz band

compare_environments

Performance by sensing environment

compare_user_counts

Performance by simultaneous user count

get_user_performance

Presence and activity performance by anonymized user slot

get_error_analysis_summary

Summary of activity and presence errors

list_samples

Browse samples with optional filters

get_sample

Resolve a sample alias or raw dataset ID

get_frozen_prediction

Retrieve stored final ensemble probabilities for a test sample

predict_sample

Run live ensemble inference on a cached sample

verify_live_prediction

Compare live output with saved ensemble probabilities

get_model_status

Device, checkpoint, loading, and artifact status

diagnose_sample

Inspect CSI integrity and prediction-vs-ground-truth behavior

find_model_errors

Search test samples by error type

find_difficult_samples

Rank samples with the retrospective uncertainty heuristic

get_capabilities

Describe the server and available analysis functions

For example, an MCP-capable assistant can answer requests such as:

Show me the final model metrics.

Which activities are most often confused?

Compare performance between 2.4 GHz and 5 GHz.

List test samples from the classroom with three users.

Run live inference on a sample and explain the prediction.

Find difficult samples where the model made an activity error.

The exact way an MCP endpoint is added depends on the client. Use the Streamable HTTP URL above rather than trying to launch mcp_server.py directly; the MCP application is mounted inside the main web server.

Local-only default and remote deployment

The default server host is 127.0.0.1, which intentionally limits access to the local machine. Do not expose the MCP endpoint directly to the public internet by simply changing the host to 0.0.0.0. A remote deployment should add appropriate authentication, TLS, origin validation, and network controls.

Generated files and Git

The repository .gitignore excludes the dataset, tensor cache, trained checkpoints, and generated results:

data/
models/
results/

This is intentional. The Git repository contains the code, project configuration, and selected hyperparameter artifact, while large or generated files are created locally by the pipeline.

If you want to distribute pretrained checkpoints separately, use a release asset or external model-storage service rather than committing large binary files directly to the repository.

Reproducibility notes

Several choices in the pipeline are deliberate:

  • Train/validation/test manifests are created with fixed random seeds.

  • Normalization statistics are calculated from the training split only.

  • Variable packet lengths are preserved and supplied to a packed BiLSTM.

  • The three training seeds are configured in config.ini.

  • Deterministic PyTorch execution can be enabled through config.ini and is enabled by default.

  • Ensemble weights are fixed and equal; they are not tuned on the validation or test split.

  • The presence threshold is configured before final testing.

  • The final evaluator hashes the selected checkpoints before opening the test data.

  • The test split is reporting-only and should not be used to make further model-selection decisions.

Exact floating-point values can still vary slightly across PyTorch, CUDA, cuDNN, GPU, and driver versions. Reproducibility should therefore be judged using the saved configuration, model-selection procedure, close numerical agreement, and consistent discrete predictions rather than assuming bit-for-bit equality across every platform.

Model limitations

The system is a research implementation, not a safety-critical people-sensing product. Presence detection is substantially stronger than fine-grained activity recognition. In the reference test evaluation, highly dynamic activities such as jump, rotation, and wave remained among the more difficult classes.

The current model input is CSI amplitude only. Complex CSI uploaded through the dashboard is reduced to magnitude before inference. Raw phase calibration, Doppler representations, additional antenna/subcarrier structure, domain adaptation, and evaluation on unseen environments or identities are reasonable directions for future work.

External sensor inference should be treated as a compatibility feature rather than a claim of hardware-independent generalization. The model expects the same 270-channel semantics and ordering used during training, and performance can degrade when the acquisition hardware or sensing domain differs from WiMANS.

WiMANS does not provide session identifiers suitable for claiming a session-independent split in this implementation. The repository therefore describes its split as stratified by environment, WiFi band, and user count rather than making a session-independence claim.

Citation

Please also cite this repository if you build on the model, preprocessing pipeline, or MCP application in your own work.

Questions and contributions

Issues and pull requests are welcome. If you report a problem, include your operating system, Python version, PyTorch version, whether CUDA is enabled, the command that failed, and the relevant traceback. That information is usually enough to reproduce most setup or runtime problems quickly.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables control of Software Defined Radios and decoding of radio protocols through an AI-friendly Model Context Protocol interface, supporting RTL-SDR and HackRF hardware for signal analysis and protocol decoding.
    24
    MIT
  • F
    license
    C
    quality
    D
    maintenance
    Enables AI to analyze and transform data using fusion algorithms with statistical, machine learning, or hybrid methods. Provides seamless data format conversion and enhanced analytical capabilities through the Model Context Protocol.
    2
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables standardized PACS-AI integration through the Model Context Protocol, providing medical imaging workflows with DICOMweb transport, hierarchical PHI de-identification, and AI model orchestration.
    Apache 2.0