Skip to main content
Glama
mpandudc

Obsidian Hybrid RAG MCP Server

by mpandudc

Obsidian Hybrid RAG MCP Server

Python MCP Embedding Reranker Vector Store License: MIT

A production-grade, zero-bloat Model Context Protocol (MCP) server providing Two-Stage Hybrid RAG (Retrieval-Augmented Generation) for Obsidian markdown knowledge vaults.

Engineered with SOTA dual-model AI (BAAI/bge-m3 1024-dim dense embeddings + jinaai/jina-reranker-v2 cross-encoder) and in-process C-extensions (sqlite-vec + SQLite FTS5 BM25), eliminating heavy external vector database daemons (Qdrant, Milvus, Chroma) and bulky abstraction frameworks (LangChain, LlamaIndex).


๐Ÿ›๏ธ Architecture Overview

                          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                          โ”‚   Obsidian Vault (.md)   โ”‚
                          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                        โ”‚ (Heading-Aware Chunker)
                                        โ–ผ
                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                 โ”‚       Single Embedded SQLite Database        โ”‚
                 โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
                 โ”‚  โ”‚    SQLite FTS5     โ”‚ โ”‚    sqlite-vec    โ”‚ โ”‚
                 โ”‚  โ”‚   (BM25 Lexical)   โ”‚ โ”‚ (1024-dim Dense) โ”‚ โ”‚
                 โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                              โ”‚                      โ”‚
                   [BM25 Lexical Hits]   [Cosine Distance Hits]
                              โ”‚                      โ”‚
                              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                         โ–ผ
                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                 โ”‚ Stage 1: Reciprocal Rank Fusion (RRF, k=60)  โ”‚
                 โ”‚ Merges lexical + semantic into Top 15 pool   โ”‚
                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                         โ”‚
                                         โ–ผ
                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                 โ”‚ Stage 2: Cross-Encoder Neural Reranker       โ”‚
                 โ”‚ (jina-reranker-v2-base-multilingual ONNX)    โ”‚
                 โ”‚ Deep query-document attention scoring        โ”‚
                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                         โ”‚
                                         โ–ผ Top K Results
                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                 โ”‚           FastMCP Stdio Interface            โ”‚
                 โ”‚  (Hermes Agent / Claude Desktop / Cursor)    โ”‚
                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Related MCP server: obsidian-rag-mcp

โœจ Key Features

  1. Anti-Bloat, In-Process Architecture:

    • Runs entirely inside a single Python process and a single SQLite file (vault-index.db).

    • Native C-extension vector similarity via sqlite-vec. Zero Docker containers, zero network ports, zero external vector service management.

  2. State-of-the-Art Dual-Model Pipeline:

    • Dense Bi-Encoder: BAAI/bge-m3 (1024 dimensions, 8,192 token context window, multi-lingual SOTA).

    • Cross-Encoder Reranker: jinaai/jina-reranker-v2-base-multilingual executed via FastEmbed ONNX Runtime with 2 execution threads.

  3. Heading-Aware Hierarchy Chunking:

    • Preserves document outline semantics (#, ##, ###, ####).

    • Context is injected with parent breadcrumbs (Title > Heading > Subheading) so vector embeddings never lose high-level context.

    • Preserves line-number traceability (L45-L78) for fast file jumps.

  4. Two-Stage Hybrid Search with Reciprocal Rank Fusion (RRF):

    • Stage 1A: SQLite FTS5 evaluates exact keyword matches, code identifiers, and acronyms using BM25.

    • Stage 1B: sqlite-vec retrieves semantically analogous concepts across multilingual vocabularies.

    • Fusion: RRF ($score = \sum \frac{1}{60 + rank}$) merges candidates into the top 15 pool.

    • Stage 2: Cross-encoder computes pairwise joint attention between the query and candidate passages, returning precision-ranked results.

  5. Sub-millisecond IPC:

    • FastMCP Stdio communication with JSON-RPC over stdin/stdout. Zero HTTP overhead.


โšก Performance & Benchmarks

Tested on Linux x86_64, 4 vCPU (Intel Xeon / AMD EPYC), 4 GB RAM:

Metric

Measured Value

Note

Query Latency (Stage 1 Hybrid)

~28 ms

FTS5 BM25 + sqlite-vec 1024-dim

Query Latency (Stage 2 Rerank)

~245 ms

Jina Reranker v2 ONNX 15 candidates

End-to-End Query Latency

< 280 ms

Cold cache to final JSON response

Incremental Sync Time

< 0.1 s

Unmodified notes bypassed via SHA256 hashes

Peak Inference Memory (RSS)

~2.4 GB

PyTorch CPU (BGE-M3) + FastEmbed ONNX

Indexing Throughput

~8-10 chunks/sec

4-thread CPU batch matrix computation


๐Ÿš€ Installation & Quickstart

1. Prerequisites

  • Python 3.10, 3.11, or 3.12

  • Recommended: uv package manager for ultra-fast virtualenv management.

2. Clone and Setup

git clone https://github.com/mpandudc/obsidian-hybrid-rag-mcp.git
cd obsidian-hybrid-rag-mcp

# Create dedicated virtualenv
uv venv .venv
source .venv/bin/activate

# Install dependencies (CPU optimized)
uv pip install torch --index-url https://download.pytorch.org/whl/cpu
uv pip install -e .

3. Environment Configuration

Create a .env or set environment variables:

# Path to your local Obsidian vault root
export VAULT_PATH="/path/to/your/obsidian-vault"

# Path to the persistent SQLite index database
export INDEX_DB_PATH="$HOME/.config/obsidian-mcp/vault-index.db"

4. Build Initial Index

Run the indexer to parse markdown notes, generate BGE-M3 dense embeddings, and populate FTS5 indices:

# Full initial build
vault-indexer --vault-path "/path/to/your/obsidian-vault" --rebuild

# Or incremental sync
vault-indexer --vault-path "/path/to/your/obsidian-vault"

๐Ÿ”Œ MCP Client Configuration

Claude Desktop (claude_desktop_config.json)

On macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
On Windows: %APPDATA%\Claude\claude_desktop_config.json
On Linux: ~/.config/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "obsidian-vault": {
      "command": "/path/to/obsidian-hybrid-rag-mcp/.venv/bin/python",
      "args": ["-m", "src.server"],
      "env": {
        "VAULT_PATH": "/path/to/your/obsidian-vault",
        "INDEX_DB_PATH": "/path/to/obsidian-hybrid-rag-mcp/vault-index.db"
      }
    }
  }
}

Hermes Agent CLI / Ecosystem

Standard Stdio:

hermes mcp add vault --command "/path/to/obsidian-hybrid-rag-mcp/.venv/bin/python -m src.server"

Shared SSE Daemon Mode (Recommended for Multi-Agent setups): Run as a background daemon or systemd service to keep models pre-warmed and share a single ~2.1 GB RAM footprint across multiple profiles or sessions:

# Start daemon listening on SSE
vault-mcp --transport sse --host 127.0.0.1 --port 8765 --preload

Register with Hermes:

hermes config set mcp_servers.vault.url http://127.0.0.1:8765/sse
hermes config set mcp_servers.vault.transport sse

Or configure via systemd user service (~/.config/systemd/user/vault-mcp.service):

[Unit]
Description=Obsidian Hybrid RAG FastMCP Daemon (SSE)
After=network.target

[Service]
Type=simple
ExecStart=/path/to/.venv/bin/python -m src.server --transport sse --host 127.0.0.1 --port 8765 --preload
Restart=always
RestartSec=5

[Install]
WantedBy=default.target

๐Ÿ› ๏ธ MCP Tools Exposed

1. search_vault

Executes Two-Stage Hybrid RAG query across your notes.

  • Parameters:

    • query (string, required): Natural language question or search phrase.

    • top_k (integer, default: 5): Number of highest-ranked passages.

    • heading_filter (string, optional): Substring filter for specific note headings (e.g. Architecture, Runbook).

  • Response: Array of candidate objects with title, rel_path, heading, lines, relevance_score, and markdown text.

2. get_note

Reads full note content with line numbering and bounded pagination to prevent context window overflow.

  • Parameters:

    • rel_path (string, required): Relative note path (e.g., projects/cuantum.md).

    • offset_line (integer, default: 1): Start line number.

    • limit_lines (integer, default: 250): Maximum lines to return.

3. sync_vault

Triggers incremental background synchronization to detect newly modified or deleted notes.

  • Parameters: None.


๐Ÿงช Testing

Run the included test suite:

pytest tests/

๐Ÿ“„ License

This project is licensed under the terms of the MIT License.

Developed with โค๏ธ by Muhammad Pandu Dwi Cahyo.

Available Tools

3 tools
get_noteGet NoteA

Retrieve full markdown content of a specific note with line number pagination.

ParametersJSON Schema
NameRequiredDescriptionDefault
rel_pathYesNote path relative to vault root (e.g. 'trading/cuantum-setup.md')
limit_linesNoMaximum lines to return in single request
offset_lineNoLine number to start reading from (1-indexed)

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden of disclosing behavior. It clearly signals a read-only operation ('Retrieve') and describes pagination behavior, which helps an agent understand it may receive partial content with limit/offset. It does not discuss error cases or side effects, but for a read tool the core behavioral traits are covered.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence with no filler. It states the action, resource, and key pagination behavior in a compact way that is easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a straightforward read tool with a complete input schema and an output schema, the description covers the essential behavior. It could be more complete by explicitly noting that full content may require multiple paginated calls and by referencing sibling tools, but the current definition is sufficient for correct invocation in most cases.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the input schema already explains rel_path, limit_lines, and offset_line. The description's mention of 'line number pagination' adds conceptual context but not new parameter-level details, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Retrieve'), a clear resource ('full markdown content of a specific note'), and a distinctive mechanism ('line number pagination'). This makes it easy to distinguish from sibling tools search_vault and sync_vault, which are clearly different operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the tool should be used when you know the exact note path and need its full content, but it does not explicitly say when to prefer it over search_vault or sync_vault. There is no mention of alternatives or exclusions, so guidance is only implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_vaultSearch VaultA

Search your Obsidian knowledge base using state-of-the-art Hybrid RAG. Combines SQLite FTS5 (BM25 lexical), BAAI/bge-m3 (1024-dim dense semantic vector), Reciprocal Rank Fusion (RRF), and Jina Reranker v2 Cross-Encoder scoring.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYesNatural language question or search phrase
top_kNoNumber of highest-ranking passages to return (default 5)
heading_filterNoOptional substring filter for document section headers

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden for behavioral disclosure. It does reveal the retrieval pipeline (BM25, dense embeddings, RRF, reranking), which is useful behavioral context. However, it does not explicitly state that the tool is read-only, mention any network/local dependencies, or note potential latency from reranking.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with a clear front-loaded purpose. The second sentence lists the technical underpinnings, which is informative but slightly dense with jargon; it still earns its place by explaining what 'Hybrid RAG' means. No redundant filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a search tool with a full input schema and an output schema, the description covers the core behavior and mechanism sufficiently. The main gap is the missing guidance on tool selection relative to siblings, and the lack of explicit safety/read-only context due to absent annotations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema fully documents all three parameters (query, top_k, heading_filter) with descriptions and defaults, so schema coverage is 100%. The description adds no parameter-level detail, but it doesn't need to since the schema already covers it.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Search your Obsidian knowledge base.' It clearly identifies the tool as a search operation, distinct from the sibling tools get_note and sync_vault. The additional technical detail (Hybrid RAG) reinforces its specialized role.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit guidance about when to use search_vault versus get_note or sync_vault. The description states what the tool does but provides no alternatives, exclusions, or contextual conditions that would help an agent decide between the siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sync_vaultSync VaultB

Trigger incremental sync on the Obsidian vault. Scans for created, modified, or deleted notes and updates dense vectors & FTS5 index.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.3/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. The description says it triggers a sync and updates vectors and indexes, but it doesn't mention whether it's safe to call (read-only vs mutating), what side effects occur (e.g., does it modify the vault or just the index?), or any performance implications. An agent cannot infer the safety profile from this sparse description.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise, with two sentences: the first states the primary action, and the second elaborates on scope and effects. It's front-loaded with the key verb, and there's no wasted words. However, it could have been structured to include a leading phrase for when to use it, but it's still efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that this tool has no parameters and an output schema exists, the description doesn't need to explain return values. However, the description lacks context on when to use it relative to siblings (e.g., after editing notes) and what the impact is. It's adequate for a simple trigger tool but could be improved by mentioning that it's for keeping search indexes up to date, which is implied but not explicit.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Since there are no parameters, the description needs to explain the scope of the sync, which it does by mentioning it's incremental and scans for created/modified/deleted notes. With zero parameters, the baseline is 4, and the description doesn't need to compensate for parameter documentation; it provides clear semantics for what the tool does without any input.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's verb ('Trigger incremental sync') and the resource ('Obsidian vault'). It also specifies what the sync does: scanning for created, modified, or deleted notes and updating dense vectors & FTS5 index. This is specific and distinguishes it from search_vault and get_note, which are read operations, though it doesn't explicitly name them as alternatives.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use this tool: whenever the vault's notes have changed and you need indexes updated. However, it doesn't explicitly state when not to use it (e.g., for full re-index) or compare it to search_vault or get_note as alternatives for different purposes. The context that it's for syncing is clear, but exclusions are absent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv1.0.0
    • First observedget_note
    • First observedsearch_vault
    • First observedsync_vault

TDQS

A3.9/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: search_vault queries, get_note retrieves a specific note, and sync_vault updates the index. There is no overlap or ambiguity between them.

Naming Consistency5/5

All tool names follow a consistent verb_noun snake_case pattern: search_vault, get_note, sync_vault. The naming is predictable and uniform.

Tool Count5/5

Three tools is well-scoped for a focused Obsidian RAG server: search, retrieve, and sync. Each tool serves a necessary function without redundancy.

Completeness4/5

The core workflow of searching, retrieving, and syncing is covered. A minor gap is the lack of an explicit listing or status tool, but users can still work around this via search.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides semantic search and keyword search over Obsidian notes, along with direct note retrieval, allowing external AI agents to query and access the vault.
    19
    BSD Zero Clause
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables semantic search and note management for Obsidian vaults via the Model Context Protocol, allowing LLMs to search, read, and index notes, PDFs, and web pages locally.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides a hybrid search engine for Obsidian vaults, enabling LLM agents to query notes with BM25 keyword and vector semantic search, metadata filtering, and sibling-document retrieval.
    5 npm
    MIT