Skip to content

Add MCP server (v1: session_query)#9

Merged
0bserver07 merged 1 commit into0bserver07:mainfrom
zh4ngx:mcp-v1
Apr 29, 2026
Merged

Add MCP server (v1: session_query)#9
0bserver07 merged 1 commit into0bserver07:mainfrom
zh4ngx:mcp-v1

Conversation

@zh4ngx
Copy link
Copy Markdown
Collaborator

@zh4ngx zh4ngx commented Apr 29, 2026

Summary

Adds a stackunderflow.mcp submodule — a Model Context Protocol server (FastMCP, stdio transport) that exposes session-log queries to MCP clients. v1 ships one tool, session_query.

Design: stateless, adapter-imported

The MCP imports stackunderflow.adapters.claude.ClaudeAdapter and parses JSONL files directly — it never touches SQLite, ingest, or ~/.stackunderflow/store.db. The adapter layer is the canonical normalised form; SQLite is downstream of it.

Why this beats wrapping the SQL store / HTTP API:

  • Schema-evolution insulated. The store schema is owned by StackUnderflow and can change without warning. The adapter contract — SessionRef + Record frozen dataclasses — is the same surface every adapter has to satisfy and is the most stable point in the codebase.
  • No ingest dependency. The MCP works whether or not the user has ever run stackunderflow init / reindex. It just reads the JSONL files that already exist on disk.
  • Stateless and fast. No connection pool, no DB lock contention, no migrations. A query is "scan files in mtime order, parse, filter, sort, return."
  • Multi-agent out of the box. v1 already iterates ~/.claude, ~/.claude-opus, ~/.claude-sonnet, ~/.claude-haiku, ~/.claude-glm (anything in Claude Code JSONL format). Adding ~/.opencode/, etc. is one line in DEFAULT_AGENT_ROOTS once a parser exists.

v1 surface

session_query(
    session_id: str | None = None,
    limit: int = 20,
    kind: Literal["tool_calls", "errors", "all"] = "all",
) -> list[dict]

Returns timestamp-sorted, most-recent-first events with: agent, project_slug, session_id, timestamp, role, model, tools, tool_calls (name + summarised args), content_preview, is_sidechain, uuid.

tool_calls filter returns only assistant records with at least one tool_use block. errors filter returns records whose tool_result blocks are flagged is_error or contain "error" / "exception" / "traceback" text.

Run

pip install -e .
stackunderflow-mcp     # stdio MCP server

Or wire into a Claude Desktop / agent config:

{
  "mcpServers": {
    "stackunderflow": {
      "command": "stackunderflow-mcp"
    }
  }
}

v2 path (not in this PR)

  • git_state(cwd) — current branch / dirty status / recent commits for a project root, so the agent can check repo state without shelling out
  • process_state(filter) — recent processes that an agent spawned (build runs, dev servers), pulling from session logs' Bash tool results

Both fit naturally as additional @mcp.tool() functions in server.py without schema changes elsewhere.

Files

  • stackunderflow/mcp/__init__.py, stackunderflow/mcp/server.py — module
  • tests/stackunderflow/test_mcp.py — 10 smoke tests
  • pyproject.tomlmcp>=1.2.0 dep, stackunderflow-mcp console script
  • requirements.txtmcp>=1.2.0
  • flake.nixpython3Packages.mcp in propagated inputs + dev shell

Test plan

  • python -m pytest tests/stackunderflow/test_mcp.py -v — 10/10 pass
  • python -m pytest tests/stackunderflow/ — 430 pass, 2 skipped, no regressions
  • ruff check stackunderflow/mcp/ tests/stackunderflow/test_mcp.py — clean
  • ruff format --check — clean
  • mypy stackunderflow/mcp/ — clean (no new errors)
  • Manual stdio smoke: python -m stackunderflow.mcp.server responds to initialize + tools/list correctly

🤖 Generated with Claude Code

@socket-security
Copy link
Copy Markdown

socket-security Bot commented Apr 29, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpypi/​mcp@​1.27.099100100100100

View full report

@zh4ngx
Copy link
Copy Markdown
Collaborator Author

zh4ngx commented Apr 29, 2026

Heads-up before further iteration on this PR

I've been exploring cortexkit/opencode-magic-context, which tackles overlapping problem space from a different angle — in-prompt active rewriting via background Historian + Dreamer agents (replaces opencode's native compaction with cache-aware deferred summarization) vs stack-underflow's observability/forum approach.

The architectures feel complementary rather than competing to me — Magic Context is the runtime memory manager, stack-underflow is the post-hoc observability layer + cross-agent knowledge base. But session_query here may overlap with their ctx_search. Want to make sure I'm not duplicating either system before going further.

Holding off on extending this MCP (was planning a git_state v2) until we sync. Happy to reframe scope to slot alongside whatever you've got going on. Will Signal you separately for a casual catch-up.

No rush — comment when you have a sec.

Adds a `stackunderflow.mcp` submodule that exposes session-log queries
to MCP clients over stdio. Stateless: parses JSONL through the existing
`stackunderflow.adapters.claude.ClaudeAdapter` directly, no SQLite or
ingest pipeline involved. The adapter layer is the canonical normalised
form; SQLite is downstream.

v1 ships one tool, `session_query(session_id, limit, kind)`, which scans
the standard agent home directories (`~/.claude`, `~/.claude-opus`,
`~/.claude-sonnet`, `~/.claude-haiku`, `~/.claude-glm`) and returns a
flat, timestamp-sorted list of recent events with summarised tool-call
arguments.

- `stackunderflow/mcp/server.py` — FastMCP server + tool implementation
- `tests/stackunderflow/test_mcp.py` — 10 smoke tests against fixture
  agent-home trees (server registration, session_id filter, tool_calls
  filter, errors filter, limit, missing-roots tolerance, arg
  truncation)
- `pyproject.toml` — adds `mcp>=1.2.0` dep + `stackunderflow-mcp`
  console script
- `requirements.txt` — adds `mcp>=1.2.0`
- `flake.nix` — adds `python3Packages.mcp` to propagatedBuildInputs
  and the dev shell

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@0bserver07
Copy link
Copy Markdown
Owner

Rebased onto current main (which has #14's E501 lint fix) so CI could go green. No code changes to your commit beyond the rebase. All checks pass — merging now. Thanks for the contribution!

@zh4ngx
Copy link
Copy Markdown
Collaborator Author

zh4ngx commented Apr 29, 2026

Thanks for the rebase! Standing by — let me know if anything else needs attention from my side.

@zh4ngx zh4ngx marked this pull request as ready for review April 29, 2026 22:33
@zh4ngx zh4ngx requested a review from 0bserver07 April 29, 2026 22:34
@0bserver07 0bserver07 merged commit 2c44d68 into 0bserver07:main Apr 29, 2026
9 checks passed
0bserver07 added a commit that referenced this pull request Apr 29, 2026
PR #9 didn't include a CHANGELOG entry. Documenting the new
`stackunderflow.mcp` module + console script + dep so it's
captured for the next tag.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@0bserver07
Copy link
Copy Markdown
Owner

Merged as 2c44d68 — thanks @zh4ngx, this is a great surface to add.

Did a real smoke test against my local logs after the merge to make sure it actually works end-to-end:

  • discover_sessions() picked up 1018 session files under ~/.claude/projects/
  • session_query(limit=5, kind="all") returned the most recent 5 events from my active session, timestamps + roles all correct
  • kind="tool_calls" filter caught real Bash invocations from this session
  • kind="errors" filter caught 3 error records

One small cosmetic note (not a regression — flagging for a future polish PR): records returned under kind="errors" come back with content_preview="". The _is_error_record heuristic correctly finds error blocks inside nested tool_result content, but content_preview is sourced from Record.content_text which doesn't include nested tool-result text. So the AI calling the tool sees "this is an error" but no preview of what the error was. Easy fix in a follow-up — surface the matched error string into the preview when kind="errors".

Also added a tiny follow-up commit (#16) with a CHANGELOG entry crediting your contribution.

For anyone reading this later: the MCP exposes one tool — session_query(session_id, limit, kind) — that any MCP client (Claude Desktop, Claude Code, Cursor) can call to introspect local Claude-Code session logs without going through StackUnderflow's SQLite store. Stateless, fast, and architecturally insulated from schema changes since it imports the adapter layer directly. Wire-up:

{
  "mcpServers": {
    "stackunderflow": { "command": "stackunderflow-mcp" }
  }
}

Then ask Claude things like "what tools did I run in the last hour" or "find my last error".

0bserver07 added a commit that referenced this pull request Apr 29, 2026
Closes #17. Andy's PR #9 shipped the MCP code but the user-facing surface
was undocumented.

- New docs/mcp.md — install, Claude Desktop / Claude Code / Cursor wire-up,
  session_query tool reference, supported agent roots, architectural notes,
  known limitations.
- README MCP section with copy-paste Claude Desktop config + pointer to
  docs/mcp.md.
- docs/cli-reference.md gains a "MCP Server" section documenting both
  `stackunderflow mcp` and the standalone `stackunderflow-mcp` console
  script; Command Overview block updated.
- New `stackunderflow mcp` Click subcommand in cli.py (delegates to
  stackunderflow.mcp.server.main) — discoverable via `stackunderflow --help`.
- docs/cross-agent-knowledge-rfc.md status note updated: the RFC is now
  partially implemented (MCP read-only query side has shipped).
- CHANGELOG entry for the subcommand + docs.

Verified: 444 tests pass, ruff clean, `stackunderflow mcp --help` resolves
to the new subcommand and starts the FastMCP server cleanly.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zh4ngx zh4ngx deleted the mcp-v1 branch April 29, 2026 23:20
@zh4ngx
Copy link
Copy Markdown
Collaborator Author

zh4ngx commented Apr 30, 2026

Thanks for actually merging + smoke-testing against your real corpus — 1018 sessions is a meaningful end-to-end signal. The kind="errors" content_preview="" quirk is real; happy to take a polish PR for that whenever, the fix is sourcing the matched error string from nested tool_result blocks into the preview field. CHANGELOG credit appreciated.

On the broader coordination question — I think session_query and your AgentDetector are genuinely complementary primitives along the artifact-vs-state and read-vs-write axes:

  • session_query answers "what artifacts did agent X produce?" by reading immutable JSONL traces — a historical read-path with no side effects
  • AgentDetector answers "what processes are running right now?" by reading live OS state — a live observability path that can optionally feed into control decisions (kill, migrate, etc.)

An orchestrator naturally wants both. I'd resist consolidating them into one tool — different substrates, different staleness profiles, different failure modes. But surfacing both via MCP and letting clients compose feels right. If Novalis already exposes AgentDetector via MCP (or could), they'd slot together cleanly without either repo needing to absorb the other.

Adjacent piece I just shipped: zellij-mcp — small Rust MCP server with 6 pane-id-addressed tools (list/spawn/send-text/read/focus/kill). Same compositional approach: it's the live write-path (control plane: spawn, send, kill) sitting orthogonal to your two read-paths, completing the trio — historical read (session_query), live read (AgentDetector), live write (zellij-mcp). Might be useful for Novalis automation if you ever want to drive panes from outside the GUI.

No rush on any of this — happy to continue async or hop on Signal when convenient.

— drafted by Kimi K2.6 (OpenCode Go) on behalf of Andy; human-reviewed before posting

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants