Skip to main content
Glama
NisargKadam

transcripts

by NisargKadam

Meeting Minutes Agent — LangGraph + local MCP servers + Pydantic

A small, terminal-only learning project. An AI agent that is an expert at minutes of meeting (MoM) and meeting summarization. It reads meeting transcripts through a local MCP file server, does arithmetic through a local MCP math server, and asks your permission before running any tool.

What you will learn:

  1. How to write a local MCP server with the official mcp SDK (FastMCP), run over stdio.

  2. The tool repository pattern in LangGraph: one object that loads tools from every MCP server and hands them to the agent.

  3. Permissions on tool execution: a LangGraph interrupt() pauses the graph, the terminal asks you y / n / a, and the graph resumes.

  4. Pydantic for settings, tool catalog rows, permission policy, and MCP tool result schemas.

No UI. No database. Three graph nodes. That is deliberate.

Architecture

                 python main.py (terminal)
                          |
                          v
   +---------------------------------------------------+
   |  LangGraph StateGraph                              |
   |                                                    |
   |   START -> agent --(tool calls?)--> permission_gate |
   |              ^                          |          |
   |              |                     interrupt()     |
   |              |                     y / n / a       |
   |              |                          v          |
   |              +------------------- run_tools        |
   |                                         |          |
   |   agent --(no tool calls)--> END        |          |
   +-----------------------------------------|----------+
                                             v
                                   ToolRepository  (agent/tool_repository.py)
                                   +-----------+-----------+
                                   | tool      | server    |
                                   | add       | math      |
                                   | read_...  | transcripts
                                   +-----------+-----------+
                                      |                  |
                            stdio     |                  |     stdio
                                      v                  v
                       mcp_servers/math_server.py   mcp_servers/file_server.py
                       add, subtract, multiply,     list_transcripts,
                       divide, average, percentage  read_transcript,
                                                    search_transcripts
                                                         |
                                                         v
                                                    transcripts/*.txt, *.md

Related MCP server: gilbert-mcp

Project layout

main.py                     terminal chat loop (entry point)
agent/
  config.py                 Settings (pydantic-settings): model, paths, MCP server commands
  models.py                 ToolSpec, PermissionRequest, PermissionMode (pydantic)
  permissions.py            PermissionPolicy (allow / ask / deny) + terminal prompt
  tool_repository.py        ToolRepository: loads MCP tools, catalog, lookup by name
  graph.py                  build_graph(): agent -> permission_gate -> run_tools -> agent
  prompts.py                system prompt for the MoM expert
mcp_servers/
  file_server.py            FastMCP "transcripts" server (read-only file access)
  math_server.py            FastMCP "math" server
transcripts/                sample meeting transcripts (.txt and .md)
tests/                      pytest suite, no API key needed

Setup

Python 3.11 or newer.

python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env               # then put your OpenAI key in .env

.env:

OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4.1-mini

Run

python main.py

You will see the tool catalog first, then a prompt. Try:

  • List the transcripts you can read.

  • Write the minutes for the sprint planning meeting.

  • Summarize the Q4 budget review and tell me the total savings agreed.

  • Across all meetings, which action items are assigned to Daniel?

  • What percentage of this week's support tickets were about checkout?

When the agent wants to run a tool marked ask, the graph pauses:

------------------------------------------------------------
  Permission required: transcripts.read_transcript
  Arguments: {"filename": "2026-09-08-sprint-planning.txt"}
------------------------------------------------------------
  Run this tool? [y]es / [n]o / [a]lways for this session:
  • y runs it once.

  • n refuses; the LLM receives "Tool call declined by user." and adapts.

  • a runs it and switches that tool to allow for the rest of the session.

How it works

1. The MCP servers (mcp_servers/)

Each server is one file. FastMCP turns plain functions into MCP tools: the type hints become the JSON schema and the docstring becomes the description the LLM reads.

mcp = FastMCP("math")

@mcp.tool(name="add")
def add_tool(a: float, b: float) -> float:
    """Add two numbers and return a + b."""
    return add(a, b)

if __name__ == "__main__":
    mcp.run(transport="stdio")

The file server returns pydantic models (TranscriptInfo, TranscriptContent, SearchHit) and refuses anything outside transcripts/ or not ending in .txt / .md. The server is the security boundary: never trust tool arguments, even from your own agent.

Inspect a server on its own with the MCP Inspector:

npx @modelcontextprotocol/inspector python mcp_servers/math_server.py

2. The tool repository (agent/tool_repository.py)

ToolRepository.from_settings() uses MultiServerMCPClient to start each server as a subprocess, do the MCP handshake, and wrap every MCP tool as a LangChain BaseTool. The repository remembers which server each tool came from and what its permission is:

Tool repository
  TOOL                 SERVER       PERMISSION  DESCRIPTION
  list_transcripts     transcripts  allow       List the meeting transcript files ...
  read_transcript      transcripts  ask         Read the full text of one transcript ...
  add                  math         ask         Add two numbers and return a + b.

The graph only ever calls repo.tools, repo.get(name) and repo.server_of(name). Add a third server in Settings.mcp_connections() and the graph does not change.

3. The permission gate (agent/graph.py)

answer = interrupt(request.model_dump())   # graph pauses here

interrupt() saves the graph state in the checkpointer and returns control to main.py, which shows the request and calls graph.ainvoke(Command(resume="yes"), config). The node re-runs from the top and this time interrupt() returns "yes". Declined calls get a ToolMessage so the conversation history stays valid for the LLM, and run_tools only executes calls that have no ToolMessage yet.

4. Pydantic (agent/models.py, agent/permissions.py, agent/config.py)

  • PermissionMode = Literal["allow", "ask", "deny"] — a typo like "maybe" fails at construction.

  • PermissionPolicyrules per tool plus a default. Edit DEFAULT_POLICY to change behaviour.

  • PermissionRequest.model_dump() — the JSON-safe payload passed through interrupt().

  • Settings(BaseSettings) — reads .env for you.

Tests

pytest -q

File

What it proves

tests/test_math_server.py

arithmetic and error cases, no MCP involved

tests/test_file_server.py

listing, reading, extension and path-traversal rejection, search

tests/test_mcp_integration.py

both servers really start over stdio and answer tool calls

tests/test_permissions.py

policy defaults, rules, session upgrade, terminal answer parsing

tests/test_tool_repository.py

catalog rows and lookup

tests/test_graph.py

interrupt payload, yes / no / always / allow / deny behaviour with a scripted fake LLM

None of the tests call OpenAI.

Exercises for students

  1. Change the policy. In agent/permissions.py set read_transcript to allow and divide to deny. Run the agent and ask it to divide something.

  2. Add a tool. Add word_count(filename) to file_server.py. Restart the agent and watch it appear in the catalog with no agent code changes.

  3. Add a write tool. Add save_minutes(filename, markdown) that writes into an output/ folder. Keep it ask. Why should write tools never be allow?

  4. Add a third server. Create mcp_servers/datetime_server.py with a days_between(a, b) tool and register it in Settings.mcp_connections().

  5. Swap the transport. Run a server with mcp.run(transport="streamable-http") and change its connection to {"transport": "streamable_http", "url": ...}.

Related MCP Connectors

Related MCP Servers