Local Intelligence Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Local Intelligence ServerExtract all dates and amounts from this invoice text."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 via official Python SDK ( |
Server name |
|
Tool |
|
Model |
|
Sampling |
|
Runtime | Python 3.13.13 · 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 |
Related MCP server: MCP LLM Integration Server
Table of contents
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:
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.
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_dataBuild 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 samplesDocument 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). 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 + | Validate surface, build prompt, call Ollama, shape errors |
Runtime | Ollama | Load model, generate tokens, never leave localhost |
Demo harness |
| Feed Loghub slices, call tool + MCP client, write |
Request lifecycle (one tool call)
Client sends
tools/callwithname=analyze_dataand{raw_text, instruction}.FastMCP deserializes arguments into the Python function.
The function builds a two-message chat (system + user).
ollama.chatposts to the local daemon; generation runs entirely on-device.Server returns the assistant
contentstring as the tool result text.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 |
| Third-party PyPI package named |
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 | 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).
D3 — Local Ollama + fixed model qwen3.5:4b
Chosen | Rejected |
| 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 |
| Log triage is a factual task; lower sampling variance | Stable structure across direct vs MCP re-run on same Apache input |
| Default Qwen3.5 CoT can exhaust the generation budget and return empty | A/B on Apache case: default think → 0 content chars / ~39 s; |
Empty content is treated as an error string so clients never receive a silent blank “success.”
D5 — Errors as return values, not raised exceptions
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 |
| Raw |
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 | 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 — |
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 ( | 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:
User → Host LLM → (sees full logs in prompt) → answerWith this server:
User → Host LLM → tools/call analyze_data(raw_text, instruction)
→ Local LLM analyzes
→ compact string returns
→ Host LLM uses summary for the userBenefits:
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:
Query → embed query → vector search over chunks → (optional rerank)
→ stuff top-k chunks into prompt → generate answerRAG 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 |
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 |
| 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.
0means 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.
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 |
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)
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
.
├── 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.txt8. Requirements
Dependency | Notes |
Linux environment | Verified on Ubuntu |
Package manager and runner | |
Python 3.13.13 | Pinned in |
Daemon listening locally | |
Model | Must appear in |
Node.js / | Only for |
ollama list | grep 'qwen3.5:4b'
# expect a line with qwen3.5:4b9. Installation
# 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.lockConfirm packages:
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:
uv run python run_demo.pyThis rewrites artifacts/, prints full model outputs, and exits non-zero if the quality gate fails.
10.2 MCP Inspector (browser)
uv run mcp dev server.pyTypical console lines:
Proxy server listening on localhost:6277
MCP Inspector is up and running at:
http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=...In the UI:
Confirm transport stdio and command defaults (
uv+run --with mcp mcp run server.py).Connect.
Open Tools →
analyze_data.Paste log text into
raw_text, setinstruction, Run Tool.
10.3 Server only (stdio)
uv run mcp run server.pyUseful when a host app launches the server as a subprocess.
10.4 Ad-hoc Python call (no MCP)
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 |
| Contents of |
|
|
11. Sample data
Real public logs from Loghub (LogPAI), downloaded 2026-07-11. See data/SOURCE.md.
Local full file | Upstream | Lines | Demo slice |
|
| 1999 |
|
|
| 1999 |
|
|
| 2000 |
|
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 |
|
Decoding |
|
Packages |
|
Network |
|
Quality gate | passed ( |
12.2 Timing table
Case | Invocation path | Elapsed | Output size | Artifact |
Apache mod_jk errors | direct | 6.53 s | 1790 chars |
|
Linux SSH auth failures | direct | 10.49 s | 2166 chars |
|
HDFS block activity | direct | 9.08 s | 2499 chars |
|
Apache (same case) | MCP stdio | 8.52 s | 1790 chars |
|
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):
[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 6Instruction: summarize dominant failure pattern; list unique errors + counts; suggest root cause from evidence only.
Model output (verbatim, 6.53 s):
### 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):
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=rootModel output (verbatim, 10.49 s):
### 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):
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 |
|
Model present |
|
Data present |
|
Run |
|
Expect |
|
Compare | Outputs may vary slightly across Ollama versions; structure and themes should match |
13. Troubleshooting
Symptom | Likely cause | Fix |
| Ollama not running |
|
Empty or near-empty answers (older code without | CoT consumed generation budget | Ensure |
| Model not pulled |
|
| CLI not on bare PATH | Always use |
Inspector fails with | Node missing | Install Node.js LTS; only required for |
Inspector auth 401 on proxy | Missing session token | Use the URL with |
Extremely slow first call | Model cold load | First |
Quality gate fails in | Empty content / Ollama error | Read the printed error string; verify model health with a tiny |
Import error | Wrong package | Uninstall standalone |
Health probes
# 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.py14. Extending the server
Phase 1 is intentionally minimal. Practical extensions:
14.1 Add another tool
@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:
index_logs(directory) → chunks + embeddings + store
search_logs(query) → top-k passages
analyze_data(passages + instruction) → still local generationThat 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):
{
"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:
Small model quality —
qwen3.5:4bcan mis-count events, invent root causes, or overstate severity (see SSHsuexample). Use for triage drafts; verify with grep/metrics for decisions.No retrieval — the model only sees the text you pass. It cannot “know” logs you did not include.
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).
Approximate structure, not schema-validated output — return type is free-form
str, not JSON with Pydantic validation.Latency — several seconds per call on local hardware (see timing table); not a sub-100ms API.
Single-process stdio — classic MCP local pattern; not a multi-tenant HTTP service with authn/z.
Data license ≠ code license — Loghub samples remain under upstream terms.
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 |
| 1.28.1 | Official MCP Python SDK, FastMCP, |
| 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.
Sample logs under data/ are from Loghub and are not re-licensed by this repository. See data/SOURCE.md.
Quick reference
# 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.pyBottom 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.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Tools
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/pypi-ahmad/add-an-llm-to-your-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server