Skip to content

feat: MCP indexing server and tree enrichment - #20

Merged
OctavianTocan merged 3 commits into
mainfrom
octavian/tools-and-assembly
Aug 3, 2026
Merged

feat: MCP indexing server and tree enrichment#20
OctavianTocan merged 3 commits into
mainfrom
octavian/tools-and-assembly

Conversation

@OctavianTocan

@OctavianTocan OctavianTocan commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Agents can index a PDF through MCP (index_pdf) without going through the Typer CLI, and the assembled tree now carries per-section summaries plus a document-level description so downstream retrieval can work from the tree alone.

After outline assembly, index() enriches nodes and then the document; assemble_tree stays structural so unit tests do not hit the LLM. Console entrypoints are split: cli for the indexer, mcp for the FastMCP server (just mcp / uv run mcp).

Note: the TOC-found indexing path is temporarily commented out, so every PDF currently takes the no-TOC chunked outline path. The local .mcp.json with a host-absolute project path was left uncommitted.

Validation

  • uv run ruff format --check agentree tests
  • uv run ruff check agentree tests
  • uv run ty check agentree tests
  • uv run pytest -q — 45 passed, 3 skipped (live Claude gates)

New concepts

FastMCP

What it is. FastMCP is a Python framework for building MCP servers: you declare tools as ordinary functions, and it handles the MCP protocol over stdio (or HTTP).

Why here. Agentree already depended on the MCP SDK; FastMCP is the shortest path to a working index_pdf tool without hand-rolling protocol wiring. The alternative was a raw MCP server with more boilerplate for the same one-tool surface.

Example from this PR. agentree/mcp/server.py registers index_pdf(path) -> str, which calls the existing index() pipeline and returns the tree string.

When not to use it. Skip FastMCP when you need a custom transport or protocol behavior the framework does not expose cleanly — then drop to the lower-level MCP SDK.

flowchart TB
  Agent[MCP client / agent] --> FastMCP[FastMCP server]
  FastMCP --> Tool[index_pdf]
  Tool --> Index[agentree.indexing.index]
  Index --> Tree[Tree JSON / str]
Loading

Compound Engineering
Cursor

Summary by Sourcery

Add MCP-based PDF indexing server and enrich assembled trees with per-node summaries and a document-level description for downstream retrieval.

New Features:

  • Expose an MCP FastMCP server with an index_pdf tool that returns the indexed document tree as a string.
  • Generate per-node summaries and a one-line document description during the indexing pipeline so trees carry semantic metadata.

Enhancements:

  • Refine the indexing pipeline to separate structural tree assembly from LLM-driven enrichment of nodes and document metadata.
  • Adjust CLI and Justfile entrypoints to distinguish the indexing CLI from the MCP server launcher.
  • Introduce strict pydantic models for node summaries and document descriptions and wire them into the Tree model.

Build:

  • Add FastMCP as a project dependency and update project scripts for cli and mcp entrypoints.

Tests:

  • Extend tree round-trip tests to cover the new DocumentDescription structure in serialized trees.

Summary by CodeRabbit

  • New Features

    • Added an MCP server for indexing PDF files and returning document trees.
    • PDF indexing now generates section summaries and an overall document description.
    • Added structured document descriptions and section summaries to indexed results.
    • Added commands and configuration for running the CLI and MCP server.
  • Improvements

    • PDF outline extraction now uses a consistent chunked-page process.
    • Updated tree data handling to include document descriptions.

OctavianTocan and others added 2 commits August 2, 2026 11:22
Expose index_pdf over MCP and split CLI/MCP entrypoints so agents can index PDFs without the Typer CLI.

Co-authored-by: Cursor <cursoragent@cursor.com>
After outline assembly, enrich each section and the whole document with LLM summaries so the tree is usable for retrieval without re-reading the PDF.

Co-authored-by: Cursor <cursoragent@cursor.com>
@semanticdiff-com

semanticdiff-com Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review changes with  SemanticDiff

Changed Files
File Status
  agentree/indexing/pdf_index.py  44% smaller
  agentree/models/tree.py  30% smaller
  .mcp.json  0% smaller
  TODO.md Unsupported file format
  agentree/cli.py  0% smaller
  agentree/indexing/prompts.py  0% smaller
  agentree/mcp/__init__.py  0% smaller
  agentree/mcp/server.py  0% smaller
  agentree/models/nodes.py  0% smaller
  justfile Unsupported file format
  pyproject.toml Unsupported file format
  tests/test_models.py  0% smaller
  uv.lock Unsupported file format

@sourcery-ai

sourcery-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds an MCP indexing server using FastMCP and enriches the PDF indexing pipeline so assembled Trees carry node-level summaries and a structured document-level description, while keeping tree assembly structural-only for testability and updating project scripts and metadata accordingly.

Sequence diagram for MCP index_pdf tool and enriched indexing pipeline

sequenceDiagram
  actor Agent
  participant FastMCP_server as FastMCP_server
  participant index_pdf
  participant index as index
  participant assemble_tree as assemble_tree
  participant generate_node_summaries as generate_node_summaries
  participant generate_doc_description as generate_doc_description

  Agent->>FastMCP_server: MCP request index_pdf(path)
  FastMCP_server->>index_pdf: invoke tool index_pdf(path)
  index_pdf->>index: index(path)
  index->>assemble_tree: assemble_tree(outline, document)
  assemble_tree-->>index: Tree
  index->>generate_node_summaries: generate_node_summaries(tree.nodes, document)
  generate_node_summaries-->>index: [nodes enriched]
  index->>generate_doc_description: generate_doc_description(tree.nodes)
  generate_doc_description-->>index: DocumentDescription
  index-->>index_pdf: Tree
  index_pdf-->>Agent: str(Tree)
Loading

File-Level Changes

Change Details Files
Extend PDF indexing pipeline to generate LLM-based node summaries and a structured document-level description after structural tree assembly.
  • Rename local Document variable for clarity and log document-level statistics using the new name.
  • Temporarily disable TOC-based indexing path by commenting out toc detection and branching logic; always use the chunked no-TOC path.
  • Log initial and continuation outlines during outline extraction for observability.
  • After assembling the structural Tree, recursively generate node summaries using an LLM completion client and attach them to nodes.
  • Generate a DocumentDescription from the assembled node list and store it on the Tree before returning.
  • Keep assemble_tree focused on structural assembly only and return a Tree without running LLM enrichment.
agentree/indexing/pdf_index.py
Introduce prompt templates and Pydantic models for document description and node summaries used by the LLM enrichment step.
  • Add GENERATE_DOC_DESCRIPTION_PROMPT guiding a one-line document-level description from the tree structure.
  • Add GENERATE_NODE_SUMMARY_PROMPT guiding summaries per node.
  • Define NodeSummary StrictModel with a summary field for node-level content.
  • Define DocumentDescription StrictModel with a description field for document-level content.
  • Update Tree.doc_description to use the new DocumentDescription type and adjust the example JSON schema accordingly.
agentree/indexing/prompts.py
agentree/models/nodes.py
agentree/models/tree.py
tests/test_models.py
Add an MCP server entrypoint using FastMCP that exposes an index_pdf tool calling the existing indexing pipeline.
  • Create agentree.mcp.server module that instantiates a FastMCP server named 'agentree'.
  • Register an index_pdf(path: str) -> str tool that calls agentree.indexing.index and returns the Tree string representation.
  • Provide a main-style runnable that starts the MCP server via mcp.run().
  • Add a lightweight agentree.mcp package init to mark the MCP server package.
agentree/mcp/server.py
agentree/mcp/__init__.py
Update packaging, dependencies, and console scripts to distinguish CLI and MCP entrypoints and depend on FastMCP.
  • Add fastmcp as a runtime dependency.
  • Rename the console script from agentree to cli and add an mcp console script pointing at the MCP server entrypoint.
  • Condense dev dependency group formatting without changing its contents.
  • Reformat keywords, ruff configuration lists, and ignores for readability while keeping semantics unchanged.
pyproject.toml
justfile
uv.lock
Minor CLI and tests adjustments to align with new types and entrypoints without changing overall behavior.
  • Remove obsolete TODO comments about MCP server entrypoint from the CLI since MCP now has its own entrypoint.
  • Update Tree round-trip test to use a DocumentDescription instance for doc_description instead of a bare string.
agentree/cli.py
tests/test_models.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@socket-security

socket-security Bot commented Aug 2, 2026

Copy link
Copy Markdown

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

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedfastmcp@​3.4.510010090100100

View full report

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

PDF indexing now creates node summaries and a document description. A new MCP server exposes PDF indexing. Tree models, prompts, command entrypoints, dependency metadata, configuration, and round-trip tests were updated.

Changes

PDF indexing and MCP integration

Layer / File(s) Summary
Tree metadata contracts and prompts
agentree/models/nodes.py, agentree/models/tree.py, agentree/indexing/prompts.py, tests/test_models.py
Added strict models for node summaries and document descriptions. Updated Tree.doc_description and its round-trip fixture. Added generation prompts.
Indexed tree generation
agentree/indexing/pdf_index.py, TODO.md
Indexing now uses chunked-page outline extraction, builds the tree, generates recursive node summaries, and assigns a document description. Marked per-page text exposure as complete.
MCP server and command wiring
agentree/mcp/*, pyproject.toml, justfile, agentree/cli.py, .mcp.json
Added the index_pdf MCP tool and stdio configuration. Added cli and mcp commands. Added fastmcp. Updated command configuration and removed an obsolete comment.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPClient
  participant index_pdf
  participant PDFIndexer
  participant CompletionClient
  MCPClient->>index_pdf: index_pdf(path)
  index_pdf->>PDFIndexer: index(path)
  PDFIndexer->>CompletionClient: generate node summaries
  CompletionClient-->>PDFIndexer: return summaries
  PDFIndexer->>CompletionClient: generate document description
  CompletionClient-->>PDFIndexer: return description
  PDFIndexer-->>index_pdf: return completed tree
  index_pdf-->>MCPClient: return tree string
Loading

Possibly related PRs

Poem

I’m a rabbit, and the tree now grows,
With summaries tucked beneath its rows.
A document description crowns the way,
While MCP opens the gate today.
Hop, index, and return the hay!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: adding an MCP indexing server and enriching the tree with generated summaries.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch octavian/tools-and-assembly

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The generate_node_summaries and generate_doc_description helpers repeatedly call asyncio.run and create a new completion client per node/description; consider making the indexing flow async or batching calls to reuse a single client and event loop for better performance.
  • In generate_doc_description, you currently pass str(nodes) to the model, which will include full Pydantic representations; it may be more efficient and predictable to serialize only the fields the model needs (e.g., titles, spans, and summaries) before sending to the LLM.
  • The page slicing logic in generate_node_summaries (doc.pages[node.start_index - 1 : node.end_index]) relies on 1-based indices with an inclusive end; consider encapsulating this in a dedicated helper or adding explicit comments to avoid off-by-one mistakes in future changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `generate_node_summaries` and `generate_doc_description` helpers repeatedly call `asyncio.run` and create a new completion client per node/description; consider making the indexing flow async or batching calls to reuse a single client and event loop for better performance.
- In `generate_doc_description`, you currently pass `str(nodes)` to the model, which will include full Pydantic representations; it may be more efficient and predictable to serialize only the fields the model needs (e.g., titles, spans, and summaries) before sending to the LLM.
- The page slicing logic in `generate_node_summaries` (`doc.pages[node.start_index - 1 : node.end_index]`) relies on 1-based indices with an inclusive end; consider encapsulating this in a dedicated helper or adding explicit comments to avoid off-by-one mistakes in future changes.

## Individual Comments

### Comment 1
<location path="agentree/indexing/pdf_index.py" line_range="142" />
<code_context>
+  """
+  # Generate node summaries.
+  for node in nodes:
+    node_text = ''.join([page.content for page in doc.pages[node.start_index - 1 : node.end_index]])
+    # TODO: Optionally retain truncated node text (e.g. first 1000 chars) on node.text.
+    # node.text = node_text[:1000]
</code_context>
<issue_to_address>
**issue (bug_risk):** Potential off-by-one error in page slicing for node_text.

If `start_index`/`end_index` are meant to be 1-based inclusive page indices, this slice will drop the last page because the slice end is exclusive. In that case you’d need `doc.pages[node.start_index - 1 : node.end_index + 1]`. Please confirm whether `end_index` is inclusive or exclusive and adjust the slicing to match, so node summaries aren’t silently truncated.
</issue_to_address>

### Comment 2
<location path="agentree/indexing/pdf_index.py" line_range="146-147" />
<code_context>
+    # TODO: Optionally retain truncated node text (e.g. first 1000 chars) on node.text.
+    # node.text = node_text[:1000]
+    # TODO: Move this completion into a dedicated generation helper.
+    summary: NodeSummary = asyncio.run(
+      create_completion_client().complete(
+        node_text, NodeSummary, system_prompt=GENERATE_NODE_SUMMARY_PROMPT
+      )
</code_context>
<issue_to_address>
**suggestion (performance):** Repeated asyncio.run calls inside generate_node_summaries can be inefficient and brittle.

Each node (and its children) currently creates a new event loop via `asyncio.run`, which will scale poorly on large trees and prevents use from existing async code. Please factor this into an async helper (e.g. `async_generate_node_summaries`) that runs under a single event loop, and have sync callers invoke it once via `asyncio.run` at the top level so you can more easily support batching/pipelining later.
</issue_to_address>

### Comment 3
<location path="agentree/mcp/server.py" line_range="22-31" />
<code_context>
+
+
+@mcp.tool
+def index_pdf(path: str) -> str:
+  """Index a PDF file into a nested section Tree.
+
+  Args:
+    path: The path to the PDF file to index.
+
+  Returns:
+    The indexed Tree.
+  """
+  return str(index(path))
+
+
</code_context>
<issue_to_address>
**issue (bug_risk):** index_pdf returns a stringified Tree, which may be an awkward format for MCP clients and mismatches the docstring.

The implementation currently returns `str(index(path))`, while the docstring promises `The indexed Tree`. For a Pydantic `Tree`, `str(tree)` is a repr-like string rather than structured JSON, which makes it harder for MCP clients to consume. Consider returning the actual `Tree` or a structured JSON form (e.g. `index(path).model_dump()` / `model_dump_json()`) so the return type matches the docstring and provides a predictable schema for clients.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

"""
# Generate node summaries.
for node in nodes:
node_text = ''.join([page.content for page in doc.pages[node.start_index - 1 : node.end_index]])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Potential off-by-one error in page slicing for node_text.

If start_index/end_index are meant to be 1-based inclusive page indices, this slice will drop the last page because the slice end is exclusive. In that case you’d need doc.pages[node.start_index - 1 : node.end_index + 1]. Please confirm whether end_index is inclusive or exclusive and adjust the slicing to match, so node summaries aren’t silently truncated.

Comment on lines +146 to +147
summary: NodeSummary = asyncio.run(
create_completion_client().complete(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Repeated asyncio.run calls inside generate_node_summaries can be inefficient and brittle.

Each node (and its children) currently creates a new event loop via asyncio.run, which will scale poorly on large trees and prevents use from existing async code. Please factor this into an async helper (e.g. async_generate_node_summaries) that runs under a single event loop, and have sync callers invoke it once via asyncio.run at the top level so you can more easily support batching/pipelining later.

Comment thread agentree/mcp/server.py
Comment on lines +22 to +31
def index_pdf(path: str) -> str:
"""Index a PDF file into a nested section Tree.

Args:
path: The path to the PDF file to index.

Returns:
The indexed Tree.
"""
return str(index(path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): index_pdf returns a stringified Tree, which may be an awkward format for MCP clients and mismatches the docstring.

The implementation currently returns str(index(path)), while the docstring promises The indexed Tree. For a Pydantic Tree, str(tree) is a repr-like string rather than structured JSON, which makes it harder for MCP clients to consume. Consider returning the actual Tree or a structured JSON form (e.g. index(path).model_dump() / model_dump_json()) so the return type matches the docstring and provides a predictable schema for clients.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
agentree/indexing/pdf_index.py (2)

48-96: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dead initial-outline assignment now that the TOC branch is disabled.

outline: Outline = Outline(sections=[]) at line 48 is never read before being overwritten at line 76, now that the TOC-found branch (which used to assign outline conditionally) is commented out. This is minor cleanup, not a functional bug, and can be addressed whenever the commented TOC path is revisited.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agentree/indexing/pdf_index.py` around lines 48 - 96, Remove the unused
initial `outline: Outline = Outline(sections=[])` assignment from the document
indexing flow, since `extract_outline_initial` now always assigns `outline`
before it is read. Leave the later outline assembly and continuation handling
unchanged.

158-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use structured JSON instead of str(nodes) for the document-description prompt.

str(nodes) sends a Python repr() of the Node list, not structured JSON. Elsewhere in this file (lines 78-80, 87-90), outlines are serialized via .model_dump_json(indent=2) before being logged or sent. Use the same approach here for a cleaner, more consistent, and more reliably parseable prompt input.

♻️ Proposed fix
   return asyncio.run(
     create_completion_client().complete(
-      str(nodes), DocumentDescription, system_prompt=GENERATE_DOC_DESCRIPTION_PROMPT
+      '\n'.join(node.model_dump_json(indent=2) for node in nodes),
+      DocumentDescription,
+      system_prompt=GENERATE_DOC_DESCRIPTION_PROMPT,
     )
   )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agentree/indexing/pdf_index.py` around lines 158 - 171, Update
generate_doc_description to serialize the nodes input with each Node’s
model_dump_json(indent=2) approach instead of passing str(nodes) to complete,
matching the structured outline serialization used elsewhere in the file while
preserving the existing prompt and response handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agentree/indexing/pdf_index.py`:
- Around line 133-156: The generate_node_summaries flow must enforce
MAX_TOKENS_PER_CHUNK before sending node_text to complete(), reusing the
existing token-aware chunking or applying the established guard without changing
summary behavior. Also refactor the per-node asyncio.run calls in
generate_node_summaries so sibling summary requests share one event loop and can
execute concurrently, while preserving recursive child-summary generation.

In `@agentree/mcp/server.py`:
- Around line 21-31: Update the index_pdf tool to serialize the indexed Tree
with its Pydantic JSON serialization method instead of wrapping index(path) in
str(). Preserve the existing indexing flow and return the result of
index(path).model_dump_json() so MCP clients receive structured, parseable JSON.
- Around line 1-12: Update the module docstring for the server startup path to
accurately describe FastMCP’s default stdio transport: remove the claim that it
starts on port 8000 and document the command as using stdio, unless the
`mcp.run()` invocation is explicitly changed to an HTTP/streamable transport
with host and port configuration.

---

Nitpick comments:
In `@agentree/indexing/pdf_index.py`:
- Around line 48-96: Remove the unused initial `outline: Outline =
Outline(sections=[])` assignment from the document indexing flow, since
`extract_outline_initial` now always assigns `outline` before it is read. Leave
the later outline assembly and continuation handling unchanged.
- Around line 158-171: Update generate_doc_description to serialize the nodes
input with each Node’s model_dump_json(indent=2) approach instead of passing
str(nodes) to complete, matching the structured outline serialization used
elsewhere in the file while preserving the existing prompt and response
handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d96e5aa7-fe63-468e-882d-19cf50f1822f

📥 Commits

Reviewing files that changed from the base of the PR and between 37ed663 and cbda7df.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • agentree/cli.py
  • agentree/indexing/pdf_index.py
  • agentree/indexing/prompts.py
  • agentree/mcp/__init__.py
  • agentree/mcp/server.py
  • agentree/models/nodes.py
  • agentree/models/tree.py
  • justfile
  • pyproject.toml
  • tests/test_models.py
💤 Files with no reviewable changes (1)
  • agentree/cli.py

Comment on lines +133 to +156
def generate_node_summaries(nodes: list[Node], doc: Document) -> None:
"""Generate summaries for a list of nodes.

Args:
nodes: The nodes to generate summaries for.
doc: The document to generate summaries for.
"""
# Generate node summaries.
for node in nodes:
node_text = ''.join([page.content for page in doc.pages[node.start_index - 1 : node.end_index]])
# TODO: Optionally retain truncated node text (e.g. first 1000 chars) on node.text.
# node.text = node_text[:1000]
# TODO: Move this completion into a dedicated generation helper.
summary: NodeSummary = asyncio.run(
create_completion_client().complete(
node_text, NodeSummary, system_prompt=GENERATE_NODE_SUMMARY_PROMPT
)
)
node.summary = summary.summary
logger.bind(node_id=node.id, node_summary=node.summary).info('Generated node summary')
# Generate summaries for the node's children.
if node.children:
generate_node_summaries(nodes=node.children, doc=doc)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Add a token budget guard before sending node_text to the completion client.

generate_node_summaries concatenates every page in a node's range and sends it as a single prompt, with no size check. chunk_pages_with_overlap exists specifically because the outline-extraction prompt must stay under MAX_TOKENS_PER_CHUNK; a single large top-level section here can hit the same overflow the chunking logic was built to avoid, and since summaries are generated once per ancestor level, the same pages get resent multiple times. For a large document, this can exceed the completion client's context limit or produce degraded summaries.

Reuse the existing token-aware chunking (or at least assert/truncate node_text against MAX_TOKENS_PER_CHUNK) before calling complete().

Separately, each node's summary is generated with its own asyncio.run() call inside a for loop, so summaries are produced strictly serially with a new event loop per call. Consider batching these with a single asyncio.run(asyncio.gather(...)) (or making generate_node_summaries itself async) so sibling summaries can run concurrently instead of one request at a time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agentree/indexing/pdf_index.py` around lines 133 - 156, The
generate_node_summaries flow must enforce MAX_TOKENS_PER_CHUNK before sending
node_text to complete(), reusing the existing token-aware chunking or applying
the established guard without changing summary behavior. Also refactor the
per-node asyncio.run calls in generate_node_summaries so sibling summary
requests share one event loop and can execute concurrently, while preserving
recursive child-summary generation.

Comment thread agentree/mcp/server.py
Comment on lines +1 to +12
"""Agentree MCP server.

This server provides an MCP endpoint for indexing PDF files into a nested section Tree.

It can be run with:

```bash
python -m agentree.mcp.server
```

It will start an MCP server on port 8000.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does fastmcp (jlowin/fastmcp) version 3.4.5 FastMCP.run() default to stdio transport when called with no arguments?

💡 Result:

Yes, in FastMCP (including version 3.4.5), the FastMCP.run method defaults to the stdio transport when called with no arguments [1][2][3]. When you invoke mcp.run without specifying a transport, the server automatically communicates through standard input and output streams, which is the standard configuration for MCP clients such as Claude Desktop [1][2][3]. If you need to use a different transport, such as HTTP, you must explicitly specify it in the method call, for example: mcp.run(transport="http", port=8000) [4][1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files =="
git ls-files | rg '(^pyproject\.toml$|^justfile$|agentree/mcp/server\.py|agentree/mcp|console)' || true

echo
echo "== server.py =="
if python3 - <<'PY'
import pathlib
p=pathlib.Path('agentree/mcp/server.py')
print(p.exists())
PY
then
  nl -ba agentree/mcp/server.py | sed -n '1,80p'
fi

echo
echo "== pyproject dependency hints =="
if [ -f pyproject.toml ]; then
  nl -ba pyproject.toml | sed -n '1,220p'
fi

echo
echo "== console script / script entrypoints =="
rg -n --hidden --glob '!*.lock' --glob '!dist/**' --glob '!build/**' 'fastmcp|mcp\.run|mcp\.write|agentree\.mcp|8000|transport' . || true

Repository: OctavianTocan/agentree

Length of output: 312


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== server.py first 80 lines =="
awk '{printf "%4d\t%s\n", NR, $0}' agentree/mcp/server.py | sed -n '1,80p'

echo
echo "== pyproject dependency and tooling hints =="
awk '{printf "%4d\t%s\n", NR, $0}' pyproject.toml | sed -n '1,220p'

echo
echo "== justfile =="
awk '{printf "%4d\t%s\n", NR, $0}' justfile

echo
echo "== relevant usages of FastMCP/MCP/server entrypoint/port =="
rg -n --hidden --glob '!*.lock' --glob '!dist/**' --glob '!build/**' 'FastMCP|mcp\.run|mcp\.write|agentree\.mcp|8000|transport|python -m agentree\.mcp\.server' . || true

Repository: OctavianTocan/agentree

Length of output: 9100


Fix the port/run docstring in agentree/mcp/server.py.

mcp.run() uses the FastMCP stdio transport by default, so neither python -m agentree.mcp.server nor the mcp script passes through a TCP port now. Either update the documented command/document that it uses stdio, or pass an HTTP/streamable transport with host/port if port 8000 is needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agentree/mcp/server.py` around lines 1 - 12, Update the module docstring for
the server startup path to accurately describe FastMCP’s default stdio
transport: remove the claim that it starts on port 8000 and document the command
as using stdio, unless the `mcp.run()` invocation is explicitly changed to an
HTTP/streamable transport with host and port configuration.

Comment thread agentree/mcp/server.py
Comment on lines +21 to +31
@mcp.tool
def index_pdf(path: str) -> str:
"""Index a PDF file into a nested section Tree.

Args:
path: The path to the PDF file to index.

Returns:
The indexed Tree.
"""
return str(index(path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Return structured JSON instead of a Python repr string.

index_pdf returns str(index(path)). Pydantic's default str() produces a repr-style string (e.g., doc_name='x.pdf' doc_description=... nodes=[...]), not JSON. For a tool whose docstring promises "The indexed Tree," return tree.model_dump_json() so MCP clients and agents receive a structured, reliably parseable payload consistent with how the rest of the codebase serializes Tree/Outline objects (e.g., agentree/indexing/pdf_index.py uses .model_dump_json(indent=2)).

🔧 Proposed fix
 def index_pdf(path: str) -> str:
   """Index a PDF file into a nested section Tree.

   Args:
     path: The path to the PDF file to index.

   Returns:
     The indexed Tree.
   """
-  return str(index(path))
+  return index(path).model_dump_json()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@mcp.tool
def index_pdf(path: str) -> str:
"""Index a PDF file into a nested section Tree.
Args:
path: The path to the PDF file to index.
Returns:
The indexed Tree.
"""
return str(index(path))
`@mcp.tool`
def index_pdf(path: str) -> str:
"""Index a PDF file into a nested section Tree.
Args:
path: The path to the PDF file to index.
Returns:
The indexed Tree.
"""
return index(path).model_dump_json()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agentree/mcp/server.py` around lines 21 - 31, Update the index_pdf tool to
serialize the indexed Tree with its Pydantic JSON serialization method instead
of wrapping index(path) in str(). Preserve the existing indexing flow and return
the result of index(path).model_dump_json() so MCP clients receive structured,
parseable JSON.

Introduced a new `.mcp.json` file to configure the MCP server for the agentree project, enabling the use of the `uv` command to run the agentree server with specified arguments.

Additionally, updated the TODO.md to mark tasks related to exposing per-page text and generating document descriptions as complete.

Co-authored-by: Cursor <cursoragent@cursor.com>
@OctavianTocan
OctavianTocan merged commit ab641e2 into main Aug 3, 2026
6 of 7 checks passed
@OctavianTocan
OctavianTocan deleted the octavian/tools-and-assembly branch August 3, 2026 21:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.mcp.json:
- Around line 5-12: Update the MCP command configuration to remove the
hard-coded /mnt/work/code/personal/agentree project path, use a portable
project-root or environment-specific setting, and invoke the registered mcp
entrypoint instead of the module path when that is the supported packaging
contract.

In `@TODO.md`:
- Around line 45-48: Keep the per-page text exposure task incomplete until
implemented. Update the indexing contract around index() to return
Document.pages alongside the Tree, then propagate and persist those pages
through storage and expose them via get_page_content; update the CLI
accordingly, or leave the TODO unchecked if this contract is not implemented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f474be38-c7d0-4141-b4c6-27bbcf6cfa05

📥 Commits

Reviewing files that changed from the base of the PR and between cbda7df and 9d02728.

📒 Files selected for processing (2)
  • .mcp.json
  • TODO.md

Comment thread .mcp.json
Comment on lines +5 to +12
"command": "uv",
"args": [
"run",
"--project",
"/mnt/work/code/personal/agentree",
"python",
"-m",
"agentree.mcp.server"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Remove the machine-specific project path.

/mnt/work/code/personal/agentree will not exist for other developers or MCP clients. Use a portable project-root configuration or an environment-specific setting, and invoke the registered mcp entrypoint when that is the supported packaging contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.mcp.json around lines 5 - 12, Update the MCP command configuration to
remove the hard-coded /mnt/work/code/personal/agentree project path, use a
portable project-root or environment-specific setting, and invoke the registered
mcp entrypoint instead of the module path when that is the supported packaging
contract.

Comment thread TODO.md
Comment on lines +45 to 48
- [x] **Expose per-page text from `index()`** — `Document.pages` already
holds tagged pages; they never leave `index()`. Storage and
`get_page_content` need them. Return pages alongside the tree (or a
small result type). (`agentree/indexing/pdf_index.py`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not mark per-page text exposure complete yet.

agentree/indexing/pdf_index.py still returns only Tree, and Document.pages remain local to index(). The CLI also persists only the tree. Either implement a result contract that carries the pages through storage and get_page_content, or keep this task open.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TODO.md` around lines 45 - 48, Keep the per-page text exposure task
incomplete until implemented. Update the indexing contract around index() to
return Document.pages alongside the Tree, then propagate and persist those pages
through storage and expose them via get_page_content; update the CLI
accordingly, or leave the TODO unchecked if this contract is not implemented.

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.

1 participant