Skip to main content
Glama
pypi-ahmad

Local Intelligence Server

by pypi-ahmad
README.md
# Local Intelligence MCP Server

**Offline server-side LLM analysis over the Model Context Protocol**

A production-shaped educational project that exposes one MCP tool — `analyze_data` — which takes arbitrary text plus a natural-language instruction, runs analysis on a **local** Ollama model (`qwen3.5:4b`), and returns the result to the MCP client. No cloud LLM calls. No embeddings. No vector database. Inference stays on `127.0.0.1:11434`.

| Field | Value |
|---|---|
| Protocol | [MCP](https://modelcontextprotocol.io) via official Python SDK (`mcp[cli]`) |
| Server name | `Local Intelligence Server` |
| Tool | `analyze_data(raw_text: str, instruction: str) -> str` |
| Model | `qwen3.5:4b` (Ollama) |
| Sampling | `temperature=0`, `think=False` |
| Runtime | Python 3.13.13 · [uv](https://docs.astral.sh/uv/) only |
| Last verified real run | **2026-07-11 08:57:55 UTC** · quality gate **passed** |

---

## Who this README is for

| Audience | What you get |
|---|---|
| **Portfolio / technical reviewers** | Architecture decisions with tradeoffs, reproducibility path, real captured outputs, honest limitations |
| **Hands-on operators** | Install, run, demo CLI, Inspector, troubleshooting, how to extend tools |
| **Tutorial learners** | MCP concepts, request lifecycle, why this is *not* RAG, line-by-line implementation flow |

---

## Table of contents

1. [Problem statement](#1-problem-statement)
2. [What this project does (and does not do)](#2-what-this-project-does-and-does-not-do)
3. [Architecture](#3-architecture)
4. [Architecture decisions (for reviewers)](#4-architecture-decisions-for-reviewers)
5. [Conceptual walkthrough (for learners)](#5-conceptual-walkthrough-for-learners)
6. [Implementation flow](#6-implementation-flow)
7. [Project layout](#7-project-layout)
8. [Requirements](#8-requirements)
9. [Installation](#9-installation)
10. [Operation](#10-operation)
11. [Sample data](#11-sample-data)
12. [Real run — evidence](#12-real-run--evidence)
13. [Troubleshooting](#13-troubleshooting)
14. [Extending the server](#14-extending-the-server)
15. [Limitations and known issues](#15-limitations-and-known-issues)
16. [Dependencies](#16-dependencies)
17. [License](#17-license)

---

## 1. Problem statement

MCP clients (Claude Desktop, Cursor, custom agents, the MCP Inspector) are excellent at orchestration, but they are not always the right place to **hold large raw payloads** or to **spend tokens re-summarizing** noisy logs. Two common patterns emerge:

1. **Client-side analysis** — the host model receives the full log dump in its context window, burns tokens, and may leak sensitive lines into a cloud provider.
2. **Cloud tool backends** — the MCP server forwards text to a remote API; convenient, but data leaves the machine and costs scale with volume.

This project implements a third pattern:

> **Server-side local intelligence:** the MCP server receives `raw_text` + `instruction`, calls a local LLM, and returns a compact analysis string. The client works with the *result*, not necessarily the full dump.

That pattern is useful for log triage, entity extraction, reformatting messy text, and any workflow where “preprocess offline on the machine that holds the data” matters more than multi-document retrieval.

---

## 2. What this project does (and does not do)

### Does

- Run an MCP server named **Local Intelligence Server**
- Register exactly one tool: **`analyze_data`**
- Build a fixed chat prompt (system + user) and call Ollama locally
- Return analysis as a string; convert exceptions into error *strings* so the client still gets a tool result
- Provide a reproducible demo (`run_demo.py`) against real public Loghub samples
- Document **verbatim** model outputs from a timed local run

### Does not

- Call OpenAI / Anthropic / any remote chat API
- Use embeddings (`nomic-embed-text`, `qwen3-embedding`, etc.)
- Implement RAG (chunking, vector index, retriever, reranker)
- Fine-tune models or use Unsloth / LoRA
- Expose multiple tools, resources, or prompt templates (Phase 1 scope)

If you need multi-document grounding over a private corpus, that is a **different architecture** (see [§5.4 Why this is not RAG](#54-why-this-is-not-rag-and-when-you-would-add-it)). This repo deliberately stays single-pass.

---

## 3. Architecture

```
┌──────────────────────────────────────────────────────────────────┐
│  MCP Client                                                       │
│  (Inspector UI · Claude Desktop · custom ClientSession · agent)   │
└───────────────────────────────┬──────────────────────────────────┘
                                │  MCP over stdio
                                │  tools/list · tools/call
                                ▼
┌──────────────────────────────────────────────────────────────────┐
│  server.py                                                        │
│  FastMCP("Local Intelligence Server")                             │
│                                                                   │
│  @mcp.tool()                                                      │
│  def analyze_data(raw_text, instruction) -> str                   │
│        │                                                          │
│        ├─ system: "You are a precise data analysis assistant."    │
│        ├─ user:   f"{instruction}\n\n{raw_text}"                  │
│        │                                                          │
│        └─ ollama.chat(                                            │
│             model="qwen3.5:4b",                                   │
│             options={"temperature": 0},                           │
│             think=False,                                          │
│           )                                                       │
│        → response["message"]["content"]  (or error string)        │
└───────────────────────────────┬──────────────────────────────────┘
                                │  HTTP POST /api/chat
                                │  127.0.0.1:11434 only
                                ▼
                     ┌─────────────────────┐
                     │  Ollama daemon       │
                     │  qwen3.5:4b weights  │
                     └─────────────────────┘
```

### Component responsibilities

| Layer | Component | Responsibility |
|---|---|---|
| Client | Inspector / agent | Discover tools, send arguments, display result |
| Transport | MCP stdio | JSON-RPC messages between client and server process |
| Server | FastMCP + `analyze_data` | Validate surface, build prompt, call Ollama, shape errors |
| Runtime | Ollama | Load model, generate tokens, never leave localhost |
| Demo harness | `run_demo.py` | Feed Loghub slices, call tool + MCP client, write `artifacts/` |

### Request lifecycle (one tool call)

1. Client sends `tools/call` with `name=analyze_data` and `{raw_text, instruction}`.
2. FastMCP deserializes arguments into the Python function.
3. The function builds a two-message chat (system + user).
4. `ollama.chat` posts to the local daemon; generation runs entirely on-device.
5. Server returns the assistant `content` string as the tool result text.
6. Client renders that text; raw logs need not remain in the client’s long-term context.

---

## 4. Architecture decisions (for reviewers)

Each decision below is intentional. Alternatives that were **rejected** are listed so the design can be evaluated, not just described.

### D1 — Official `mcp[cli]` FastMCP, not the standalone `fastmcp` package

| Chosen | Rejected |
|---|---|
| `from mcp.server.fastmcp import FastMCP` after `uv add "mcp[cli]"` | Third-party PyPI package named `fastmcp` (different maintainer lineage) |

**Why:** Tutorial and SDK docs refer to FastMCP **bundled inside** the official MCP Python SDK. Installing both packages confuses imports and versions. Verified import path against installed **`mcp` 1.28.1**.

### D2 — Single tool, single pass

| Chosen | Rejected |
|---|---|
| One tool `analyze_data` | Tool sprawl (separate summarize / extract / format tools) or multi-agent graphs |

**Why:** Phase 1 teaches “LLM inside an MCP tool.” One well-described tool with free-form `instruction` covers summarize, extract, format, and pattern-finding without combinatorial API surface. Extra tools can be added later (see [§14](#14-extending-the-server)).

### D3 — Local Ollama + fixed model `qwen3.5:4b`

| Chosen | Rejected |
|---|---|
| `qwen3.5:4b` only | Cloud chat APIs; silent fallbacks to other tags; embedding models |

**Why:** Portfolio consistency with other local projects; 4B fits common laptop GPUs/CPU budgets; no API keys in the critical path. The model name is pinned in code so demos do not silently drift.

### D4 — `temperature=0` and `think=False`

| Setting | Rationale | Evidence |
|---|---|---|
| `temperature=0` | Log triage is a factual task; lower sampling variance | Stable structure across direct vs MCP re-run on same Apache input |
| `think=False` | Default Qwen3.5 CoT can exhaust the generation budget and return **empty** `message.content` while still HTTP 200 | A/B on Apache case: default think → **0** content chars / ~39 s; `think=False` → **1790** chars / ~7 s (2026-07-11) |

Empty content is treated as an error string so clients never receive a silent blank “success.”

### D5 — Errors as return values, not raised exceptions

```python
except Exception as e:
    return f"Error calling local LLM: {e}"
```

**Why:** MCP tool failures that raise often surface as opaque transport errors. Returning a string keeps the tool contract (`-> str`) and gives the client something to show the user (e.g. “connection refused to Ollama”).

### D6 — uv-only environment, Python 3.13.13

| Chosen | Rejected |
|---|---|
| `uv venv`, `uv add`, `uv run`, pin `3.13.13` | Raw `pip` + `python -m venv` as the primary path |

**Why:** Reproducible lockfile (`uv.lock`), consistent with the rest of the local portfolio toolchain.

### D7 — No RAG in this repo

Retrieval adds index management, chunk quality, embedding cost, and a second failure mode (retrieve-wrong → generate-confidently-wrong). For **single blob + instruction** analysis, stuffing the blob into the user message is the simplest correct design. Reviewers should judge this as **scoped correctness**, not missing features.

### Decision summary matrix

| Concern | Decision | Primary tradeoff |
|---|---|---|
| SDK | Official `mcp[cli]` | Slightly heavier install than a micro-server |
| Model | Local 4B Qwen | Weaker than frontier cloud models; fully private |
| Decoding | temp 0, no think | Less creative; more reliable content field |
| Scope | One tool | Less API surface; less specialization |
| Data plane | Logs in tool args | Fine for KB-scale slices; not for multi-GB dumps |

---

## 5. Conceptual walkthrough (for learners)

### 5.1 What is MCP?

The **Model Context Protocol** is a standard way for AI applications (hosts/clients) to talk to **capability servers**. A server can expose:

| Primitive | Role | Used here? |
|---|---|---|
| **Tools** | Callable functions with schemas (side effects or computation) | **Yes** — `analyze_data` |
| **Resources** | Readable data URIs (files, records) | No |
| **Prompts** | Reusable prompt templates | No |

Think of MCP like a USB standard for AI tools: the client does not hard-code every vendor API; it lists tools, reads JSON schemas, and calls them.

### 5.2 Client vs server vs model

| Piece | Runs where | Knows about |
|---|---|---|
| **MCP client** | Host app | User intent, tool selection, conversation UI |
| **MCP server** (`server.py`) | Separate process (stdio) | How to call Ollama, how to shape the tool |
| **LLM** | Ollama process | Tokens in → tokens out; no MCP awareness |

The model never “speaks MCP.” The server translates MCP tool calls into `ollama.chat` and back.

### 5.3 What “server-side analysis” means

Without this server, a typical flow is:

```text
User → Host LLM → (sees full logs in prompt) → answer
```

With this server:

```text
User → Host LLM → tools/call analyze_data(raw_text, instruction)
                      → Local LLM analyzes
                      → compact string returns
                 → Host LLM uses summary for the user
```

Benefits:

- **Privacy:** raw lines stay on the machine running Ollama.
- **Token budget:** host context holds analysis, not megabytes of logs (when the host chooses not to re-paste them).
- **Specialization:** the tool’s system prompt is fixed for “precise data analysis.”

### 5.4 Why this is not RAG (and when you would add it)

**RAG (Retrieval-Augmented Generation)** is a pipeline roughly:

```text
Query → embed query → vector search over chunks → (optional rerank)
      → stuff top-k chunks into prompt → generate answer
```

| RAG component | Purpose | In this project? |
|---|---|---|
| Document loader | Ingest corpus | No (client passes text) |
| Chunker | Split long docs | No (40-line slices for demos) |
| Embedding model | Map text → vectors | No |
| Vector store | Index / search | No |
| Retriever / reranker | Select context | No |
| Generator LLM | Answer from context | **Yes** — but context is the whole `raw_text` argument |

**This project is tool-augmented generation with a local LLM**, not RAG. The “context” is supplied explicitly as `raw_text`. That is the right pattern when:

- the user (or agent) already has the exact text to analyze, or
- the corpus is small enough to fit in one call.

You would **add RAG** when:

- the knowledge base is large and unknown to the caller,
- you need citations from many documents,
- or repeated queries should not re-send the full corpus each time.

A future extension could still live *behind* MCP (e.g. `search_logs` tool + `analyze_data`), but that is out of Phase 1 scope.

### 5.5 System vs user message design

| Role | Content | Why |
|---|---|---|
| **system** | Fixed persona: precise data analysis assistant | Stabilizes style and task framing |
| **user** | `{instruction}` then blank line then `{raw_text}` | Puts the operator’s goal first; data second |

Putting the instruction first helps the model treat the log as evidence for a stated task rather than free-form “continue the log.”

### 5.6 Temperature and thinking (decoded)

- **Temperature** controls randomness. `0` means greedy / near-deterministic decoding — preferred for ops-style extraction.
- **Thinking / CoT** (Qwen3.5) can produce a long private reasoning trace. If the runtime budgets tokens primarily for that trace, the **visible answer field can be empty**. Disabling think keeps tokens for `content`.

### 5.7 Stdio transport in one sentence

The MCP host starts `uv run mcp run server.py` as a child process and speaks JSON-RPC on stdin/stdout. No open network port is required for the default local setup (Inspector adds a local proxy for the browser UI).

---

## 6. Implementation flow

Map the code path in `server.py` to the lifecycle above.

```text
1. Import FastMCP from official SDK
2. Instantiate mcp = FastMCP("Local Intelligence Server")
3. Decorate analyze_data with @mcp.tool()  → schema + description auto-derived
4. On call:
     a. try:
     b.   ollama.chat(model, messages, options, think=False)
     c.   read message.content
     d.   if empty → error string
     e.   else return content
     f. except → error string
5. mcp CLI loads module, finds global `mcp`, runs server.run()
```

### Line-level map (`server.py`)

| Lines | Code intent |
|---|---|
| 1–2 | Imports: FastMCP + Ollama client |
| 4 | Server identity string shown to clients |
| 7–12 | Tool registration + docstring (appears in `tools/list`) |
| 14–15 | Comment documenting the empty-content failure mode |
| 16–24 | Local chat completion with pinned model and decoding flags |
| 25–28 | Content extraction and empty-content guard |
| 29–30 | Exception → client-visible error string |

There is **no** `if __name__ == "__main__"` block. `uv run mcp run server.py` imports the module, locates the `mcp` object, and calls `run()` for you.

### Demo harness flow (`run_demo.py`)

```text
for each Loghub 40-line slice:
    analyze_data(raw, instruction)     # direct Python call
    write artifacts/<id>_direct.txt

MCP ClientSession over stdio:
    list_tools → expect ["analyze_data"]
    call_tool(apache case)
    write artifacts/mcp_stdio_apache.txt

write artifacts/run_summary.json + full_outputs.json
assert quality gate (non-empty, no errors)
```

---

## 7. Project layout

```text
.
├── server.py                 # MCP server + analyze_data
├── run_demo.py               # Reproducible real-run harness
├── main.py                   # uv init stub (unused by MCP path)
├── pyproject.toml            # project metadata + deps
├── uv.lock                   # locked graph
├── .python-version           # 3.13.13
├── LICENSE                   # MIT (code)
├── README.md                 # this file
├── data/
│   ├── SOURCE.md             # Loghub provenance
│   ├── apache_access_sample.log
│   ├── linux_syslog_sample.log
│   ├── hdfs_sample.log
│   ├── apache_40.log         # analysis slice
│   ├── linux_40.log
│   └── hdfs_40.log
└── artifacts/                # outputs from real runs
    ├── run_summary.json
    ├── full_outputs.json
    ├── apache_mod_jk_errors_direct.txt
    ├── linux_ssh_auth_failures_direct.txt
    ├── hdfs_block_activity_direct.txt
    └── mcp_stdio_apache.txt
```

---

## 8. Requirements

| Dependency | Notes |
|---|---|
| Linux environment | Verified on Ubuntu |
| [uv](https://docs.astral.sh/uv/) | Package manager and runner |
| Python **3.13.13** | Pinned in `.python-version` |
| [Ollama](https://ollama.com) | Daemon listening locally |
| Model **`qwen3.5:4b`** | Must appear in `ollama list` |
| Node.js / `npx` | Only for `mcp dev` (Inspector UI) |

```bash
ollama list | grep 'qwen3.5:4b'
# expect a line with qwen3.5:4b
```

---

## 9. Installation

```bash
# From the project root
uv python install 3.13.13    # if needed; fallback policy is 3.12.10 only if 3.13.13 is unresolvable
uv venv --python 3.13.13
uv python pin 3.13.13
uv sync                      # restores mcp[cli] + ollama from uv.lock
```

Confirm packages:

```bash
uv run python -c "
import importlib.metadata as m
from mcp.server.fastmcp import FastMCP
print('mcp', m.version('mcp'))
print('ollama', m.version('ollama'))
print('FastMCP OK', FastMCP)
"
```

Expected (as of the documented run): **mcp 1.28.1**, **ollama 0.6.2**.

---

## 10. Operation

### 10.1 One-command real demo (recommended first)

With Ollama up and `qwen3.5:4b` available:

```bash
uv run python run_demo.py
```

This rewrites `artifacts/`, prints full model outputs, and exits non-zero if the quality gate fails.

### 10.2 MCP Inspector (browser)

```bash
uv run mcp dev server.py
```

Typical console lines:

```text
Proxy server listening on localhost:6277
MCP Inspector is up and running at:
  http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=...
```

In the UI:

1. Confirm transport **stdio** and command defaults (`uv` + `run --with mcp mcp run server.py`).
2. **Connect**.
3. Open **Tools** → `analyze_data`.
4. Paste log text into `raw_text`, set `instruction`, **Run Tool**.

### 10.3 Server only (stdio)

```bash
uv run mcp run server.py
```

Useful when a host app launches the server as a subprocess.

### 10.4 Ad-hoc Python call (no MCP)

```bash
uv run python -c "
from pathlib import Path
from server import analyze_data
raw = Path('data/apache_40.log').read_text()
print(analyze_data(raw, 'List unique error messages and approximate counts.'))
"
```

### 10.5 Example tool arguments

| Argument | Example |
|---|---|
| `raw_text` | Contents of `data/linux_40.log` |
| `instruction` | `Extract attacking IPs and recommend firewall actions in bullets.` |

---

## 11. Sample data

Real public logs from **[Loghub](https://github.com/logpai/loghub)** (LogPAI), downloaded **2026-07-11**. See `data/SOURCE.md`.

| Local full file | Upstream | Lines | Demo slice |
|---|---|---:|---|
| `data/apache_access_sample.log` | `Apache/Apache_2k.log` | 1999 | `data/apache_40.log` |
| `data/linux_syslog_sample.log` | `Linux/Linux_2k.log` | 1999 | `data/linux_40.log` |
| `data/hdfs_sample.log` | `HDFS/HDFS_2k.log` | 2000 | `data/hdfs_40.log` |

Upstream URLs:

- https://raw.githubusercontent.com/logpai/loghub/master/Apache/Apache_2k.log  
- https://raw.githubusercontent.com/logpai/loghub/master/Linux/Linux_2k.log  
- https://raw.githubusercontent.com/logpai/loghub/master/HDFS/HDFS_2k.log  

40-line slices keep prompts inside a practical context budget for a 4B local model while remaining realistic (error storms, SSH noise, healthy HDFS INFO).

**License note:** Loghub data is third-party. This repo’s MIT license covers **project code**, not the log corpus.

---

## 12. Real run — evidence

### 12.1 Run metadata

| Field | Value |
|---|---|
| Timestamp (UTC) | **2026-07-11 08:57:55** |
| Model | `qwen3.5:4b` |
| Decoding | `temperature=0`, `think=False` |
| Packages | `mcp` 1.28.1 · `ollama` 0.6.2 |
| Network | `POST http://127.0.0.1:11434/api/chat` → **HTTP 200** on all calls |
| Quality gate | **passed** (`artifacts/run_summary.json` → `"all_ok": true`) |

### 12.2 Timing table

| Case | Invocation path | Elapsed | Output size | Artifact |
|---|---|---:|---:|---|
| Apache mod_jk errors | direct `analyze_data` | **6.53 s** | 1790 chars | `artifacts/apache_mod_jk_errors_direct.txt` |
| Linux SSH auth failures | direct `analyze_data` | **10.49 s** | 2166 chars | `artifacts/linux_ssh_auth_failures_direct.txt` |
| HDFS block activity | direct `analyze_data` | **9.08 s** | 2499 chars | `artifacts/hdfs_block_activity_direct.txt` |
| Apache (same case) | MCP stdio `tools/call` | **8.52 s** | 1790 chars | `artifacts/mcp_stdio_apache.txt` |

MCP path also listed tools as `['analyze_data']` with **`isError: false`**. Direct and MCP Apache outputs matched (same structure and length under `think=False` + `temperature=0`).

---

### 12.3 Case 1 — Apache httpd / mod_jk (failure storm)

**Input sample** (`data/apache_40.log`):

```text
[Sun Dec 04 04:47:44 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
[Sun Dec 04 04:47:44 2005] [error] mod_jk child workerEnv in error state 6
[Sun Dec 04 04:51:08 2005] [notice] jk2_init() Found child 6725 in scoreboard slot 10
...
[Sun Dec 04 04:51:18 2005] [error] mod_jk child workerEnv in error state 6
```

**Instruction:** summarize dominant failure pattern; list unique errors + counts; suggest root cause from evidence only.

**Model output (verbatim, 6.53 s):**

```text
### 1. Dominant Failure Pattern
The dominant failure pattern is the continuous cycling of Apache JK (mod_jk) backend workers into an **unrecoverable "error" state** despite repeated initialization attempts by `workerEnv.init()`. While new child processes are successfully spawned and registered in the scoreboard, they consistently fail to transition from a healthy or transient error state 6 directly to a stable operational state.

### 2. Unique Error Messages and Approximate Counts
Based on the provided log snippet:

*   **`[error] mod_jk child workerEnv in error state 6`** (Approximately 10 occurrences)
    *   This is the most frequent specific error, appearing repeatedly across different timestamps (e.g., at 04:51:18 and 04:52:15). It indicates a persistent failure condition where the worker cannot recover.
*   **`[error] mod_jk child workerEnv in error state 7`** (Approximately 3 occurrences)
    *   This appears as an escalation or alternative manifestation of the same underlying issue, seen specifically at timestamp `04:52:15`.

*(Note: The logs also contain numerous `[notice] jk2_init() Found child...` and `[notice] workerEnv.init() ok ...` messages indicating successful spawning and initial configuration checks.)*

### 3. Most Likely Root Cause
The root cause is likely a **configuration mismatch or resource exhaustion** preventing the JK workers from completing their initialization sequence, as evidenced by `workerEnv.init()` returning "ok" while the subsequent state check immediately reports an error in states 6 and 7. This suggests that although the Apache process starts successfully, the backend application (Java/Tomcat) running within these children fails to bind or start due to missing ports, insufficient memory, or a deadlock during startup logic.
```

**Reviewer note:** counts are approximate model estimates, not deterministic regex tallies. Good for triage; not a substitute for `grep -c`.

**MCP re-run:** same case via `ClientSession` produced the same analysis body (`artifacts/mcp_stdio_apache.txt`, 8.52 s, `isError=false`).

---

### 12.4 Case 2 — Linux syslog SSH noise

**Input sample** (`data/linux_40.log`):

```text
Jun 14 15:16:01 combo sshd(pam_unix)[19939]: authentication failure; ... rhost=218.188.2.4
Jun 15 02:04:59 combo sshd(pam_unix)[20882]: authentication failure; ... rhost=220-135-151-1.hinet-ip.hinet.net  user=root
```

**Model output (verbatim, 10.49 s):**

```text
### 1) Security-Relevant Events Identified
*   **SSH Authentication Failures:** Multiple repeated failures detected via `sshd(pam_unix)` indicating brute-force or credential stuffing attempts against the root user (`user=root`).
    *   Source: Jun 15 02:04:59 (IP: 220-135-151-1.hinet-ip.hinet.net) – **7 instances**.
    *   Source: Jun 15 12:12:34 & 12:13:19 (IP: 218.188.2.4) – **Multiple instances** (~16 total).
*   **Successful Privilege Escalation:** Two `su` sessions opened by root (`uid=0`) for non-root users, indicating successful compromise or authorized admin activity.
    *   User: `cyrus` (Jun 15 04:06:18–20).
    *   User: `news` (Jun 15 04:12:42–43).

### 2) Distinct Remote Hosts/IPs Involved in Auth Failures
*   **220-135-151-1.hinet-ip.hinet.net** (IPv6 mapped notation for `220.135.151.1`) – Associated with high-volume root login attempts at 02:04 UTC on Jun 15.
*   **218.188.2.4** (IPv4) – Associated with repeated authentication failures starting Jun 14 and continuing through the morning of Jun 15.

### 3) Non-Auth System Events
*   **Service Activity:** Successful `su` sessions for users `cyrus` and `news`.
*   **Log Rotation Alert:** `logrotate: ALERT exited abnormally with [1]` at Jun 15 04:06:20. This indicates a configuration or disk space issue preventing log rotation, which may be causing syslog buffer issues given the high volume of auth logs generated during the attack window.

### 4) Ops Triage Recommendation
*   **Immediate Action:** Block both remote IPs (`218.188.2.4` and `220-135-151-1.hinet-ip.hinet.net`) at the firewall level immediately to stop active credential stuffing attacks on root accounts.
*   **Investigation Priority 1 (Compromise):** Verify if users `cyrus` or `news` were authorized by an admin who used their credentials for `su`. If unauthorized, assume a successful breach and initiate incident response procedures (reset passwords, audit user activity).
*   **Log Management:** Investigate the `logrotate` failure. With such high log volume from SSH attacks, disk space exhaustion is likely; ensure logs are being archived or rotated to prevent system instability during future attack waves.
```

**Honest limitation:** labeling `su` as possible “compromise” is **over-inference** — the log alone does not prove unauthorized access. A careful operator treats that as a hypothesis, not a finding of fact. The IP extraction and auth-failure theme are well grounded.

---

### 12.5 Case 3 — HDFS healthy INFO traffic

**Model output (verbatim, 9.08 s):**

```text
Based on the provided HDFS NameNode/DataNode style logs, here is the analysis:

### 1. Summary of Block/Packet Operations
The log snippet depicts a standard **block replication and storage lifecycle** within an HDFS cluster. The operations observed include:
*   **Block Allocation:** New blocks are being allocated for tasks (specifically `/user/root/rand/_temporary/...`), indicated by `NameSystem.allocateBlock`. This suggests active data processing or job execution where new files need to be stored immediately upon creation.
*   **Data Reception & Replication:** Multiple DataNodes (`dfs.DataNode$PacketResponder`) are receiving blocks from various remote peers (e.g., `/10.251.x.x`). The consistent block size of `67108864` bytes indicates standard 64MB replication chunks.
*   **Storage Registration:** As data is received, the NameNode (`dfs.FSNamesystem`) updates its in-memory map to track these blocks as "stored," linking specific DataNodes (e.g., `10.251.73.220:50010`) to their respective block IDs via `NameSystem.addStoredBlock`.
*   **Verification:** A single instance of successful verification (`dfs.DataBlockScanner`), confirming data integrity for a specific block ID before it was likely committed or moved in the broader context (though this log only shows success).

### 2. Unique Components and Thread Names
The logs contain explicit thread/component identifiers following the standard Hadoop logging format `Component$Subcomponent`. The unique components identified are:
*   **dfs.DataNode$PacketResponder**: Handles incoming block data packets for specific blocks or general termination of responder threads.
*   **dfs.FSNamesystem**: Manages high-level filesystem operations, including adding stored blocks to the map and allocating new blocks.
*   **dfs.DataNode$DataXceiver**: Specifically handles the network reception stream (`Receiving block ... src: ... dest: ...`).
*   **dfs.DataBlockScanner**: Responsible for verifying data integrity (checksums) of specific blocks.

*(Note: While timestamps like `081109` and thread IDs in packet responders exist, they are not unique component names).*

### 3. Health Status Assessment
**The snippet looks healthy.**

*   **Evidence:** All log entries are prefixed with the level `INFO`. There are no lines containing `WARN`, `ERROR`, or any other severity indicators indicating a failure or warning condition. The operations (allocation, reception, verification) completed successfully without interruption in this timeframe.
```

**Why this case matters:** a useful analyzer must also say **“healthy”** when appropriate, not invent incidents. The model correctly treated an INFO-only window as non-alarming.

---

### 12.6 Reproducibility checklist

| Step | Command / artifact |
|---|---|
| Environment | `uv sync` + Python 3.13.13 |
| Model present | `ollama list` includes `qwen3.5:4b` |
| Data present | `data/*_40.log` from Loghub slices |
| Run | `uv run python run_demo.py` |
| Expect | `all_ok: true` in `artifacts/run_summary.json` |
| Compare | Outputs may vary slightly across Ollama versions; structure and themes should match |

---

## 13. Troubleshooting

| Symptom | Likely cause | Fix |
|---|---|---|
| `Error calling local LLM: ... Connection refused` | Ollama not running | `ollama serve` or start the desktop service; check port 11434 |
| Empty or near-empty answers (older code without `think=False`) | CoT consumed generation budget | Ensure `server.py` passes `think=False` (current code does) |
| `model 'qwen3.5:4b' not found` | Model not pulled | `ollama pull qwen3.5:4b` — do not silently swap tags if following this repo’s model policy |
| `mcp: command not found` outside uv | CLI not on bare PATH | Always use `uv run mcp ...` |
| Inspector fails with `npx not found` | Node missing | Install Node.js LTS; only required for `mcp dev` |
| Inspector auth 401 on proxy | Missing session token | Use the URL with `MCP_PROXY_AUTH_TOKEN` printed at startup; header is `x-mcp-proxy-auth: Bearer <token>` |
| Extremely slow first call | Model cold load | First `ollama.chat` after boot loads weights; subsequent calls are faster |
| Quality gate fails in `run_demo.py` | Empty content / Ollama error | Read the printed error string; verify model health with a tiny `ollama run qwen3.5:4b "ping"` |
| Import error `mcp.server.fastmcp` | Wrong package | Uninstall standalone `fastmcp` if present; reinstall `mcp[cli]` via uv |

### Health probes

```bash
# Ollama up?
curl -s http://127.0.0.1:11434/api/tags | head

# Tool path only
uv run python -c "from server import analyze_data; print(analyze_data('a=1\nb=2','Sum the numbers'))"

# MCP path only
uv run python run_demo.py
```

---

## 14. Extending the server

Phase 1 is intentionally minimal. Practical extensions:

### 14.1 Add another tool

```python
@mcp.tool()
def count_error_lines(raw_text: str) -> str:
    """Deterministic count of lines containing ERROR/Error/error."""
    n = sum(1 for line in raw_text.splitlines() if "error" in line.lower())
    return f"error_line_count={n}"
```

Keep deterministic helpers separate from LLM tools so reviewers can tell which path is probabilistic.

### 14.2 Optional file-path tool

Accept a path under a sandboxed directory, read with size limits, then call the same chat helper. Always enforce:

- path confinement (no `../../etc/passwd`)
- max bytes
- allowlist of extensions

### 14.3 Toward RAG (only if requirements demand it)

A second-phase design might look like:

```text
index_logs(directory) → chunks + embeddings + store
search_logs(query)    → top-k passages
analyze_data(passages + instruction) → still local generation
```

That adds embedding model choice, index freshness, and evaluation of retrieval quality **separately** from generation. Do not fold that complexity into `analyze_data` without measuring need.

### 14.4 Host configuration sketch

Example Claude Desktop-style config shape (paths will differ):

```json
{
  "mcpServers": {
    "local-intelligence": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/project", "mcp", "run", "server.py"]
    }
  }
}
```

---

## 15. Limitations and known issues

Be explicit with stakeholders:

1. **Small model quality** — `qwen3.5:4b` can mis-count events, invent root causes, or overstate severity (see SSH `su` example). Use for triage drafts; verify with grep/metrics for decisions.
2. **No retrieval** — the model only sees the text you pass. It cannot “know” logs you did not include.
3. **Context limits** — full 2k-line Loghub files are stored for provenance; demos use 40-line slices. Multi-MB dumps need chunking or map-reduce style tooling (not implemented).
4. **Approximate structure, not schema-validated output** — return type is free-form `str`, not JSON with Pydantic validation.
5. **Latency** — several seconds per call on local hardware (see timing table); not a sub-100ms API.
6. **Single-process stdio** — classic MCP local pattern; not a multi-tenant HTTP service with authn/z.
7. **Data license ≠ code license** — Loghub samples remain under upstream terms.
8. **Nondeterminism residual** — even at temperature 0, backend/version changes can alter wording; do not treat outputs as cryptographic.

---

## 16. Dependencies

Managed exclusively with **uv** (`pyproject.toml` / `uv.lock`):

| Package | Version (locked run) | Role |
|---|---|---|
| `mcp[cli]` | 1.28.1 | Official MCP Python SDK, FastMCP, `mcp` CLI |
| `ollama` | 0.6.2 | Python client for local Ollama HTTP API |

Transitive highlights: `pydantic`, `httpx`, `anyio`, Starlette/SSE stack for SDK features.

**Not used:** standalone `fastmcp`, Unsloth, embedding libraries, cloud SDKs.

---

## 17. License

Project source code and documentation are released under the [MIT License](LICENSE).

Sample logs under `data/` are from [Loghub](https://github.com/logpai/loghub) and are **not** re-licensed by this repository. See `data/SOURCE.md`.

---

## Quick reference

```bash
# install
uv sync

# prove the stack
uv run python run_demo.py

# interactive MCP
uv run mcp dev server.py

# headless MCP server
uv run mcp run server.py
```

**Bottom line for reviewers:** this is a small, fully offline MCP server that correctly places a local LLM *behind* a single well-scoped tool, ships real Loghub-based evidence, and documents the failure mode (`think` vs content) that would otherwise look like a silent bug. Scope is intentional; RAG is deliberately out of band until retrieval is a real requirement.

<p align="center">Made with ❤️ by Ahmad Mujtaba</p>

TDQS

A4.1/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no risk of confusion between tools. The single tool's purpose is clearly distinct by default.

Naming Consistency5/5

A single tool trivially satisfies naming consistency. The name 'analyze_data' follows a clear verb_noun pattern.

Tool Count2/5

A server named 'Local Intelligence Server' with only one tool feels under-scoped. Typically such servers offer multiple operations, making one tool too few for the apparent domain breadth.

Completeness4/5

The single tool covers the core purpose of text analysis via various instructions, but lacks specialized tools for common subtasks like data retrieval or different analysis modes, resulting in minor completeness gaps.

Maintenance

ActivityMaintained
ResponsivenessNo issues