@gresmcp/mcp
v1.5.0
Published
From raw docs to refined answers — Gresy fires your documents into solid Postgres knowledge, entirely local, your MCP.
Maintainers
Readme
gresmcp (@gresmcp/mcp)
From raw docs to refined answers — Gresy fires your documents into solid Postgres knowledge, entirely local, your MCP.
A Postgres-backed knowledge source (ks) MCP server for AI tools, with a management CLI.
Use it to create, manage and share a knowledge bases (documentation, notes, specs) across all your projects and AI tools.
gresmcp— CLI: create/edit/delete knowledge sources, feed files/folders/url or manual textmcp— FastMCP server: read-only search/query tools scoped to the knowledge sources you pass on the command line
Features
- Hybrid search — pgvector semantic similarity +
tsvectorfull-text search fused via Reciprocal Rank Fusion (RRF);vector-only andkeyword-only modes also available, with results comparable across knowledge sources - Graceful degradation (
automode) — the default search mode runs hybrid and automatically falls back to keyword-only for knowledge sources whose Ollama is unreachable (network-level failures only), reported viastructuredContent.fallback, a text notice and a server warning; the server default is settable withmcp --mode/GRESMCP_SEARCH_MODE - Fully local & private — documents stay in your PostgreSQL database and are embedded by your own Ollama server; no cloud, no third parties.
- Many input types — feed files, folders, whole websites or manual text/stdin notes
- Document conversion built in
- HTML (Readability main-content extraction),
- PDF (heading/list/table detection, page metadata),
- Word
.docx(via mammoth) and.doc(via word-extractor), - EPUB ebooks (via epub2, chapters converted to Markdown),
- Markdown, code and plain text — all chunked as clean Markdown
- Encrypted file support — password-protected PDFs and Word documents via
--file-password - Website crawling — same-host crawling with
sitemap.xml/robots.txtdiscovery, URL glob filters, CSS-scoped content extraction (--html-filter) and pluggable crawler backends (crawlee,playwright,puppeteer) incl. JS-shell retry - Idempotent feeding — unchanged chunks are skipped on re-feed;
--replaceforces a clean re-ingest;--dry-runpreviews without writing - Live progress — per-file progress bar plus end-of-run summary statistics while feeding
- Immutable embeddings — the embedding model and its dimension are locked per knowledge source, so vectors always stay consistent
- Multiple knowledge sources — one table per ks; different embedding models/dimensions coexist and the MCP server can search several ks at once
- MCP server — read-only FastMCP server with
search,get_entry,list_entries,list_sources,list_tagsandlist_kstools, over stdio or HTTP streaming, hard-scoped to the ks names you pass - Metadata & tags — attach tags/custom metadata at feed time, filter MCP search results by them and discover the tags in use (
list_tagstool,gresmcp tags <ks>) - Structured output — MCP
searchalways returns MCP-specstructuredContent(tool declares anoutputSchema), so schema-conformant clients can consume results without any special parameter;format:'json'only switches the text payload to serialized JSON. The CLIsearchandentriescommands offer--jsonfor scripts - Configurable chunking — chunk size and overlap tunable per feed
- Environment check —
gresmcp checkverifies Node, Postgres, pgvector, schema, Ollama and embedding models in one shot (script-friendly exit code,--jsonoutput) - Zero-config schema — the database schema is created automatically on first run (
gresmcp initrepairs it)
Requirements
- Node.js >= 20
- PostgreSQL with the
pgvectorextension installed (defaultgresdatabase andgresusergrespassword used in the examples bellow) - An Ollama server with an embedding model pulled (e.g.
ollama pull nomic-embed-text(english only) orollama pull bge-m3(multilingual))
Run gresmcp check --model <model> to verify all of these at once (it also creates the vector extension for you if it is missing).
Installation
npm install -g @gresmcp/mcp or use it directly with npx -y @gresmcp/mcp ... (MCP server) / npx -y -p @gresmcp/mcp gresmcp ... (CLI).
Configuration
Environment variables:
| Variable | Default | Description |
| --- | --- | --- |
| GRESMCP_DATABASE_URL | postgresql://gres:gres@localhost/gres | Postgres connection string |
| GRESMCP_OLLAMA_URL | http://localhost:11434 | Default Ollama base URL |
| GRESMCP_KS | – | Comma-separated ks names for the MCP server (fallback when no arguments are passed) |
| GRESMCP_SEARCH_MODE | auto | Default search mode for the MCP server (auto, hybrid, vector or keyword; --mode wins) |
Quickstart
# 0. Verify your setup (Node, Postgres, pgvector, schema, Ollama, embedding model)
gresmcp check --model nomic-embed-text
# 1. Create a knowledge source named `docs` (probes Ollama to validate the model and detect its dimension)
gresmcp ks create docs --model nomic-embed-text --description "Project documentation"
# 2. Feed it the `docs` knowledge source
gresmcp feed docs --path ./docs # a folder (text, markdown, code, html, pdf, doc)
gresmcp feed docs --path ./README.md # a single file (text, markdown, code, html, pdf, doc)
gresmcp feed docs --text "Note: the API key lives in vault" --title "API keys" --tags secrets
gresmcp feed docs --url https://example.com/my-document.docx # a single remote file (text, markdown, code, html, pdf, doc)
gresmcp feed docs --url https://example.com/docs/ --max-pages 200 --depth 3 # scrape a website
gresmcp feed docs --url https://example.com/ --sitemap sitemap-only # ingest exactly the sitemap.xml / robots.txt URLs
gresmcp feed docs --url https://example.com/ --sitemap 'nav.docs a' # only follow links inside <nav class="docs">
gresmcp feed docs --url https://example.com/ --url-filter '**/docs/**' --url-filter '**/*.md' # only follow matching links
gresmcp feed docs --url https://example.com/blog/ --html-filter 'article.post -> .entry-content' # only ingest matching HTML regions
# 3. Serve it to your AI tool over MCP
mcp docs # stdio transport, scoped to the 'docs' knowledge source
mcp --all # every knowledge source in the database
mcp docs,wiki --transport http --port 8080 # HTTP streaming on http://localhost:8080/mcp
mcp docs --mode keyword # pin keyword-only search (no Ollama needed at query time)
# 4. Find help about all the commands
gresmcp --help
mcp --helpThe database schema is created automatically on first run (CREATE EXTENSION vector, ks table, one entry table per knowledge source).
CLI reference
gresmcp init
Explicitly initializes/repairs the schema (runs automatically for every command anyway).
gresmcp check --model <model> [--url <url>] [--probe] [--json]
Verifies that your environment meets all requirements:
| Check | Description |
| --- | --- |
| Node | Node.js >= 20 |
| Postgres | Database reachable at GRESMCP_DATABASE_URL |
| pgvector | Extension installed (created automatically if missing); warns below 0.5.0, which is required for HNSW indexes |
| schema | ks table and every knowledge source's entry table exist (gresmcp init repairs them) |
| Ollama | Server reachable at the configured URL |
| models | The embedding model given via --model plus the embedding model of every existing knowledge source is pulled at the right URL; --probe also embeds a test string to verify the dimension |
gresmcp check --model nomic-embed-text
# ✔ Node v22.14.0 (>= 20 required)
# ✔ PostgreSQL reachable at postgresql://gres:gres@localhost/gres (PostgreSQL 16.4)
# ✔ pgvector 0.8.0 installed
# ✔ schema ready (2 knowledge source(s))
# ✔ Ollama reachable at http://localhost:11434 (5 model(s))
# ✔ model 'nomic-embed-text' available at http://localhost:11434 (ks 'docs')
# all checks passedgresmcp ks create <name> --model <model> [--url <url>] [--description <text>] [--dim <n>]
Creates a knowledge source. The Ollama server is probed to validate the model and detect the embedding dimension (skip the probe with --dim). The embedding model and dimension are immutable afterwards.
gresmcp ks edit <name> [--url <url>] [--rename <new>] [--description <text>]
Edits a knowledge source. The Ollama server URL, name and description can change; the model cannot.
gresmcp ks list [--json]
Lists knowledge sources with model, dimension, URL and entry counts.
gresmcp ks stats <name> [--json]
Shows the statistics of a knowledge source: entry chunk count, distinct sources, total content volume with average/min/max chunk length, when it was last fed, plus the ks metadata (model, dimension, Ollama URL).
gresmcp ks delete <name> [--yes]
Deletes a knowledge source and drops its entry table (asks for confirmation unless --yes).
gresmcp feed <ks> (--path <path> | --url <url> | --text <text> | --text-file <path> | --stdin) [options]
Feeds data into a knowledge source. Exactly one input of --path, --url, --text, --text-file or --stdin must be given.
| Option | Description |
| --- | --- |
| --path <path> | File or folder. Folders are walked recursively (skips node_modules, .git, dist, ...). Supported: text, markdown, code files, HTML (converted to Markdown via turndown; head description/keywords/author/lang metadata harvested), PDF (converted to Markdown with heading/list/table detection via pdf2md; page count, image-only page count and the /Info author are stored in metadata), Word documents (.docx converted to Markdown via mammoth with headings/tables/lists, .doc plain-text extraction; docx core-properties author/lang recorded — legacy .doc files have no property extraction), EPUB ebooks (.epub parsed via epub2; spine chapters are converted to Markdown with TOC titles as headings, chapter count/author/language stored in metadata). Binaries and empty files are skipped with a warning. |
| --url <url> | Scrape a website (http/https). Follows same-host links from the seed, converts HTML to Markdown (main-content extraction via Readability) and ingests linked text/markdown / text/plain resources as well as application/pdf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document and application/epub+zip responses. Each page becomes a source keyed by its URL, so re-feeding is idempotent. Only 2XX responses are ingested; failures are reported as skipped. |
| --crawler <name> | Crawler backend for --url: auto (default), crawlee (static HTTP), playwright or puppeteer (full browser rendering for SPAs; must be installed globally, e.g. npm i -g playwright && npx playwright install chromium). With auto, pages that look like empty JS shells are retried with Playwright when available. |
| --sitemap <mode> | Link discovery for --url: auto (default) uses the site's sitemap.xml (then robots.txt sitemaps, incl. nested index files) when available and crawls exactly those URLs, falling back to HTML link crawling when no sitemap exists; sitemap-only does the same but ingests nothing (with a warning) when no sitemap is found; robots-only skips /sitemap.xml and uses only sitemaps listed in robots.txt (useful when a site's root sitemap.xml exists but doesn't cover the target pages — e.g. OpenStax books, whose real page sitemaps are only referenced from robots.txt), warning and ingesting nothing when robots.txt lists none; html-only always follows links; any other value is treated as a CSS selector — links are only followed inside matching elements on each page (e.g. nav.docs a), with a warning if the selector matches no links anywhere. Sitemap mode respects --max-pages and --url-filter (--depth is not applicable — sitemap URLs have no hierarchy). |
| --max-pages <n> | Max pages to fetch when crawling (default 999) |
| --depth <n> | Max link depth from the seed URL (default 5; 0 = seed page only) |
| --url-filter <glob> | Only follow --url links whose URL matches the glob. Repeatable — a link is followed if it matches any glob. Matched against the full URL, case-insensitive; * doesn't cross /, ** does. The seed URL itself is always fetched and ingested; non-matching links are never fetched and don't count towards --max-pages. Example: --url-filter '**/docs/**' |
| --html-filter <selector> | Limit HTML content extraction to a CSS selector — any selector supported by the DOM works (e.g. '#article > .content', 'article.post', '#article .content:first-child'). Use -> to chain several selectors and scope step by step: '#article -> .content' takes the first #article, then all .content inside it. Applies to --path HTML files and --url pages. Intermediate steps take the first match; the final step keeps all matching elements (joined in document order). When set, the automatic Readability main-content extraction is bypassed for pages. Noise elements (script, nav, aside, footer, ...) are removed before matching, so child pseudo-classes like :first-child see the cleaned DOM. Files/pages where the selector matches nothing are skipped (html filter '<selector>' matched nothing). |
| --file-password <password> | Password for encrypted files — PDFs (user-password protected) and Word documents (encrypted .docx/.doc). Applies to --path and --url ingestion. Owner-restricted PDFs without a user password (readable but with copy/print flags set) are read without it. |
| --text <text> / --text-file <path> / --stdin | Manual entry text (chunked the same way as documents; auto-titled unless --title is given). --text-file reads a UTF-8 or UTF-16 (BOM) file — the file is only transport, the entry keeps manual semantics (default manual source, auto-title). --stdin is BOM-tolerant too, which makes --stdin < note redirects and PowerShell pipes safe. |
| --title <title> | Title for manual entries (default: derived from the first heading line, else the first non-empty line, truncated to 80 chars with …) |
| --source-name <name> | Source name for manual entries (default manual) |
| --tags a,b | Tags stored in metadata, filterable by MCP search; merged (case-insensitively deduplicated) with each document's own tags — for --url, the page's HTML keywords/og:article:tag |
| --tags-from-path | --path only: derive tags from the folder segments and the file name stem relative to the given path root — docs/python/utils.md fed with --path docs gets python and utils; if the file already has tags (HTML keyword meta), everything is unioned |
| --metadata k=v ... | Repeatable custom metadata entries |
| --replace | Replace all chunks of the same source instead of skipping unchanged ones |
| --dry-run | Parse and chunk only; no embeddings, no writes |
| --strict | Exit with code 2 when any file/page was skipped or failed, or when nothing was ingested; exit 0 when at least one document was ingested cleanly, 1 on hard errors such as a missing target (script-friendly; see below). Idempotent unchanged-chunk skips never count. |
| --chunk-size <n> | Max chunk length in characters (default 1200) |
| --overlap <n> | Chunk overlap in characters (default 180) |
Feeding is idempotent: a chunk whose (source, chunk_index, content hash) already exists is skipped, so re-running a feed only adds new content. Use --replace to force a clean re-ingest of a source.
With --strict, feed uses script-friendly exit codes. 0 — at least one document was ingested and no file/page failed (unsupported-extension walk entries excluded alongside ingested documents don't count). 1 — a hard error (missing --path target, bad arguments, database unreachable, embedding failure mid-run). 2 — any file/page was skipped or failed (parse errors, HTTP errors, unreadable/empty/binary files, no extractable text, --html-filter misses, ...), or nothing at all was ingested — every file/page skipped counts, including unsupported-extension exclusions, because there is nothing else. Unchanged-chunk skips from idempotent re-feeds never affect the exit code. --dry-run combines with --strict, which makes it a pre-flight check for scripted loops:
gresmcp feed docs --path ./handbook --dry-run --strict || exit 1Manual entries (--text/--text-file/--stdin) are auto-titled from their first ATX heading line (any # level), else their first non-empty line, truncated to ~80 characters with an ellipsis — no need to pass --title for quick notes. Every chunk also records its ingest time as created_at, which the MCP server surfaces in search results (fed=...), get_entry output and list_entries; list_sources reports first_fed_at/last_fed_at per source, which is how you tell apart multiple notes sharing the default manual source.
You can try a real live example and test it out:
gresmcp ks create business --model bge-m3 --description "It's all about business"
gresmcp feed business --url https://openstax.org/books/introductory-business-statistics-2e/pages/1-introduction --sitemap robots-only --url-filter "https://openstax.org/books/introductory-business-statistics-2e/**" --html-filter main
# and then check it out your ks
gresmcp search business "Venn diagrams" --limit 5Once you connect gresmcp` to your AI tool, You can start chatting with it like: "Tell me about Venn diagrams", and receive answers.
Windows PowerShell encoding notes
PowerShell 5.1 re-encodes text piped into native executables using $OutputEncoding, which defaults to ASCII — non-ASCII characters become ? before gresmcp ever sees them — and PowerShell does not support the < input redirect. To avoid the temp-file dance entirely:
- Preferred: write the note to a file and use
--text-file(no pipe involved, UTF-8 or PowerShell's UTF-16Out-Fileoutput both work):gresmcp feed docs --text-file note.md - A raw-byte redirect via
cmdalso works (<in cmd passes file bytes untouched):cmd /c "gresmcp feed docs --stdin < note.md" - If you pipe from PowerShell, fix the encoding once in your
$PROFILE:$OutputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
While feeding, live progress is shown on stderr: a bar for the currently processed file (chunks processed, chars) plus a total line with files processed/total, cumulative chars and the number of rejected/ignored files — rejected files are reported but excluded from the progress percentage. The total line also carries an estimated time remaining (eta 1m12s) extrapolated from the chars processed so far (hidden during the first second and in the final summary line). Up to three files render concurrently (one bar each); lines are truncated to the terminal width, so long URLs never wrap and break the in-place redraw. In a non-interactive terminal (or when piped), plain per-file lines are printed instead. After a successful feed, the summary line reports exactly what was added: fed '<ks>': N chunk(s) inserted (X chars), M unchanged chunk(s) skipped, ... (--dry-run prints the chunk count and chars that would be inserted instead). A this run summary additionally prints the same statistics as gresmcp ks stats (chunks, distinct sources, chars, avg/min/max per chunk), scoped to the chunks written during that run.
For --url, page titles come from <title>/og:title/<h1> and each page's metadata records source_url, crawled_at, the meta description (if present), tags, plus author (from meta[name=author], dcterms.creator or article:author; URL-valued entries are ignored) and lang (from <html lang>, the content-language meta or og:locale, reduced to its two-letter primary subtag). Linked PDFs are ingested too: a PDF page is parsed to Markdown (pdf_pages / pdf_image_only_pages / author metadata recorded), while image-only (scanned) PDFs with no text layer are skipped with a clear reason. Linked Word documents (.docx/.doc) are ingested as well — doc_format metadata records the detected format, and docx core properties contribute author/lang (legacy .doc has none). Linked EPUBs (.epub, served as application/epub+zip) are parsed like local files (epub_chapters / author / lang metadata recorded). With --crawler playwright|puppeteer, PDFs, Word documents and EPUBs are detected by their URL extension and downloaded directly (browsers would otherwise render PDFs in the internal viewer or download DOC files); use auto/crawlee for content-type-accurate detection. Servers mislabeling downloads as application/octet-stream are handled by sniffing the docx/doc magic bytes (or, for .epub URLs, by handing the payload to the EPUB parser). Crawling stays on the seed's hostname; non-HTTP links and obvious binary URLs are skipped. With --url-filter, only same-host links matching at least one glob are followed (the seed itself is exempt). Requests are made without consulting robots.txt, and CWD is never polluted (crawlee state goes to a temp directory).
gresmcp search <ks> <query> [--mode <mode>] [--limit <n>] [--tags <tags>] [--meta <k1=v1,k2=v2>] [--json]
Searches a knowledge source from the command line — same ranking as the MCP search tool (hybrid pgvector + tsvector via RRF). hybrid and vector modes need the ks's Ollama server reachable (query embedding); keyword works with Postgres alone; auto (default) runs hybrid and degrades to keyword per ks when Ollama is unreachable (a [auto] … fell back to keyword mode notice is printed to stderr).
| Option | Description |
| --- | --- |
| --mode <mode> | auto (default), hybrid, vector or keyword |
| --limit <n> | Max results (default 8) |
| --tags a,b | Any-of tags metadata filter |
| --meta k1=v1,k2=v2 | Metadata filter; all pairs must match (values compared as text) |
| --json | Print an array of hits (ks, id, source, title, chunk_index, score, snippet, metadata, created_at) instead of the human-readable ranked list |
$ gresmcp search docs "how do I feed documents" --limit 2
1. [docs] id=42 score=0.0312 fed=2026-09-09T10:15:00.000Z (README.md#chunk0)
title: gresmcp
A Postgres-backed knowledge source (ks) MCP server for AI tools, with a management CLI...
$ gresmcp search docs "how do I feed documents" --json | jq '.[0].id'
42gresmcp entries <ks> [--source <source>] [--limit <n>] [--offset <n>] [--json]
Lists the entry chunks of a knowledge source — same output as the MCP list_entries tool.
| Option | Description |
| --- | --- |
| --source <source> | Filter by source (e.g. a file path or scraped URL) |
| --limit <n> | Max entries (default 20) |
| --offset <n> | Skip entries (default 0) |
| --json | Print an array of entries (id, source, title, chunk_index, chars, created_at) instead of the column table |
$ gresmcp entries docs --json | jq '.[0]'
{ "id": 42, "source": "README.md", "title": "gresmcp", "chunk_index": 0, "chars": 1204, "created_at": "2026-09-09T10:15:00.000Z" }gresmcp tags <ks> [--limit <n>] [--json]
Lists the distinct tags of a knowledge source with entry counts, sorted by usage — same output as the MCP list_tags tool.
| Option | Description |
| --- | --- |
| --limit <n> | Max tags (default 50, sorted by usage) |
| --json | Print an array of tags (tag, entries_count) instead of the column table |
$ gresmcp tags docs
ENTRIES TAG
3 api
1 setupgresmcp prune <ks> (--ids <ids> | --source-name <name>) [--yes]
Removes entries from a knowledge source. Exactly one of:
| Option | Description |
| --- | --- |
| --ids <ids> | Comma-separated entry chunk IDs to delete (e.g. 12,13,14); IDs that do not exist are ignored and reported |
| --source-name <name> | Deletes every entry chunk of this source (e.g. a file path or a scraped URL) |
Without --yes, prune prints how many entry chunks and how many characters will be removed and asks for confirmation; with --yes it deletes immediately. Either way the final line reports the entry chunks and characters actually removed.
The manual source (the default source name for feed --text/--text-file/--stdin entries) cannot be pruned via --source-name — pass --ids with its entry IDs instead. Manual entries fed with a custom --source-name are prunable by name.
MCP server reference
mcp <ks1,ks2,...> [--all] [--mode auto|hybrid|vector|keyword] [--transport stdio|http] [--port <n>] [--host <addr>]The server is read-only and hard-scoped to the knowledge sources named at startup; unknown names abort with a helpful message. --mode (or the GRESMCP_SEARCH_MODE env var, auto by default) sets the default search mode for search calls that omit mode — an explicit per-call mode still wins. list_ks reports the effective default as search_mode.
| Tool | Description |
| --- | --- |
| search | Auto (default) / hybrid / vector / keyword search. Returns ranked chunks with [ks] id, score, fed=<ingest timestamp>, source#chunk, title and a snippet. Supports ks (array), mode, limit (1–50, default 8), tags (any-of metadata filter), meta (all-must-match key→value metadata filter) and format (controls only the text payload: text default = human-readable ranked list, json = the structured content serialized as JSON; structuredContent conforming to the tool's outputSchema is always returned regardless of format). |
| get_entry | Full content, metadata and created_at of one chunk by id. |
| list_entries | Paginated entry list (id, source, title, chunk index, size, created_at), optional source filter. |
| list_sources | Ingested documents with chunk counts, first_fed_at/last_fed_at timestamps and the union of tags per source. |
| list_tags | Distinct tags of one ks with entry counts, sorted by usage (default limit 50). ks is optional when the server is scoped to a single ks; use the result with the tags filter of search. |
| list_ks | The configured knowledge sources with entry counts. |
Hybrid mode fuses pgvector cosine ranking and ts_rank_cd full-text ranking with Reciprocal Rank Fusion (score = Σ 1/(60 + rank)), so results are comparable across knowledge sources even when they use different embedding models. hybrid and vector modes need the ks's Ollama server reachable (the query is embedded at search time); when Ollama is down, retry with mode: "keyword" — it works with Postgres alone.
The default auto mode does this for you: it runs hybrid and, when a ks's Ollama is unreachable (connection refused / network timeout only — HTTP errors and missing models still fail loudly), that ks is re-searched with keyword instead, so queries keep returning results. The degradation is reported three ways: an optional fallback object ({ mode: "keyword", ks: [...], reason }) in the structuredContent, a [auto] Ollama unreachable for ks '…' — fell back to keyword mode (…) notice prepended to the text payload, and a warning on the server's stderr.
Using with AI tools
Please always mention in AGENTS.md which resources are included in the gresmcp knowledge base, so the agent aware what to look for and where.
opencode
{
"mcp": {
"gresmcp": {
"type": "local",
"command": ["npx", "-y", "@gresmcp/mcp", "ks1,ks2,ks3"],
"enabled": true
}
}
}With a global install you can use the binary directly: ["mcp", "ks1,ks2"]. To scope via environment instead of arguments: "command": ["npx", "-y", "@gresmcp/mcp", "--all"] or set GRESMCP_KS=ks1,ks2.
Claude Desktop / generic MCP config
{
"mcpServers": {
"gresmcp": {
"command": "npx",
"args": ["-y", "@gresmcp/mcp", "ks1,ks2"],
"env": {
"GRESMCP_DATABASE_URL": "postgresql://gres:gres@localhost/gres"
}
}
}
}HTTP transport
mcp ks1 --transport http --port 8080 --host 127.0.0.1
# MCP endpoint: http://localhost:8080/mcp (health check: /health)Binds dual-stack by default (--host ::): both 127.0.0.1 and [::1] (and the other network interfaces) reach the server. Pass --host 127.0.0.1 for loopback-only. /health answers text/plain; charset=utf-8, and /favicon.ico serves the bundled icon. Requires IPv6 support; on hosts without it, the default falls back to a 0.0.0.0 (IPv4-only) bind with a warning.
Database layout
ks— one row per knowledge source:id,name(unique),description,embedding_model(immutable),embedding_dim,ollama_url, timestampsentry_<uuid>— one table per knowledge source (per-ks tables allow different vector dimensions across models):id,source,title,chunk_index,content,content_hash,metadata jsonb, generatedtsv tsvector,embedding vector(dim)- Indexes:
GIN(tsv),HNSW(embedding vector_cosine_ops),btree(source)
Development
npm install
npm run build # tsup -> dist/cli.js, dist/mcp.js
npm run typecheck # tsc --noEmit
npm test # vitest (DB integration tests skip gracefully when Postgres/pgvector is unavailable)Release checklist: npm version <x.y.z> && npm publish --access public (the scoped package name @gresmcp/mcp must be owned by your npm org). npm version keeps src/version.ts (the VERSION constant reported by the bins) in sync automatically.
Contributors - THANK YOU
gresmcp is built on top of these great projects and tools:
| Contributor | Description | | --- | --- | | FastMCP | The standard framework for building Model Context Protocol (MCP) servers, clients, and interactive applications (gresmcp uses its TypeScript port) | | Turndown | An HTML to Markdown converter written in JavaScript | | PostgreSQL | The world's most advanced open source database | | Zod | TypeScript-first schema validation library with static type inference | | OpenCode | The open source AI coding agent | | OpenChamber | Agentic development environment for AI coding across desktop, browser, phone, and VS Code | | Ollama | The easiest way to automate your work using open models, while keeping your data safe | | Nano | The fast, feeless and eco-friendly digital currency | | NanoGPT | Subscription-free, pay-as-you-go access to huge selecion of AI models with strict retention policy, privacy and server location | | Crawlee | Crawlee helps you build and maintain your crawlers. It's open source, but built by developers who scrape millions of pages every day for a living. | | pdf2md | Amazing JavaScript npm library to parse PDF files and convert them into Markdown | | Mammoth | Convert .docx documents, such as those created by Microsoft Word, Google Docs and LibreOffice, and convert them to HTML. | | word-extractor | Read data from Word .doc files (97-2003) in pure Node.js — no Office installation required | | office-crypto | Decrypt password-protected MS Office files (DOCX/DOC), a TypeScript port of msoffcrypto-tool | | epub2 | Parse EPUB ebook files in Node.js — metadata, spine order and chapter extraction | | {{ YOU }} | This could be you — issues and PRs welcome at gitea.com/fairking/gresmcp |
