tachy
A self-hosted knowledge engine that enables Claude Code to fetch, analyze, and store structured lessons learned from support tickets/issues via MCP tools, leveraging hybrid search for relevant prior cases.
README
tachý
A self-hosted, source-agnostic knowledge engine for engineering work items. It ingests support tickets / issues from pluggable sources (Freshdesk, GitHub... with more to come), lets an LLM agent turn them into structured, queryable "lessons learned", and retrieves relevant prior cases when a new item comes in.
The LLM agent is the reasoning layer. This service only persists and retrieves; it never calls an LLM itself. Each item is read and structured once, then reused.
The server speaks plain MCP, so any MCP-compatible client works (Codex CLI, other agents). This README assumes Claude Code as the client; nothing here is Claude-specific.
Architecture
PowerShell -> Claude Code --(MCP stdio)--> tachy MCP server -> core -> Postgres
\-> sources/* -> Freshdesk / GitHub / ...
teammates / cron / CI --(HTTP)--> Hono REST API -> core ----^
packages/core: DB, theWorkItemSourceinterface, services (source-agnostic)packages/sources/freshdesk: Freshdesk adapterpackages/sources/github: GitHub Issues adapterpackages/mcp: MCP server for Claude Code or any MCP client (the primary surface)packages/api: Hono REST API (cron, teammates, future UI)packages/cli:synccommanddb/schema.sql: canonical Postgres schema
Prerequisites
- Node 20+
- PostgreSQL 14+ with the
vector(pgvector),pg_trgm, andpgcryptoextensions available;schema.sqlcreates them. The easiest way to get pgvector locally is thepgvector/pgvectorDocker image, orapt install postgresql-16-pgvector.
Setup
npm install
# Create the database and apply the schema:
createdb tachy
psql "postgres://localhost:5432/tachy" -f db/schema.sql
cp .env.example .env # then fill in DATABASE_URL and your Freshdesk token
schema.sql seeds the teams/products you use (Track & Trace -> tpd, ftrace;
BPT -> csdr, eudr, pcf, medical-devices), the osapiens-freshdesk source, and
an example group mapping (Freshdesk group 48000641379 -> tpd). Adjust the
seed block for your other groups.
Freshdesk numeric
status(e.g. 6) is account-specific and stored raw.
Use it from Claude Code (PowerShell)
.mcp.json already registers the server (Claude Code's own config format;
other MCP clients register it differently). From the project folder:
claude
Then, in the session:
analyze ticket 58925 from osapiens-freshdesk
Claude will call fetch_work_item, clean + summarize, show you the summary,
and only call save_knowledge_entry after you approve. To consult:
what do we know that's relevant to ticket 61010?
Claude calls get_context (fetch + archive search) and answers. Optionally:
post that analysis as a private note on 61010
MCP tools
Core loop: fetch_work_item, search_knowledge, get_context,
save_knowledge_entry, post_private_note, add_knowledge_feedback,
record_analysis_run.
search_knowledge and get_context are hybrid: keyword (FTS + trigram) blended
with semantic similarity over a local embedding, so paraphrases surface even
with no shared keywords. save_knowledge_entry stamps created_by from
TACHY_USER_EMAIL, embeds the entry on save, and is customer-blind:
identity never enters the searchable text or the embedding (see "Customers
and versions" below).
Curated vocabulary, so Claude never invents categories from a ticket alone:
list_resolution_patterns / add_resolution_pattern,
list_components / add_component, list_customers / add_customer.
Correction: set_work_item_customer, set_observed_version.
REST API (optional)
npm run api # http://localhost:8787
GET /health, POST /work-items/:source/:id/fetch, GET /knowledge/search?q=,
POST /knowledge, GET /knowledge/:id/feedback, POST /knowledge/:id/feedback,
POST /analysis-runs, POST /work-items/:source/:id/notes,
PATCH /work-items/:id/customer, PATCH /work-items/:id/observed-version,
GET|POST /resolution-patterns, GET|POST /products/:slug/components,
GET|POST /customers.
Set TACHY_API_TOKEN to require a bearer token on every route except /health
(Authorization: Bearer <token>). If it is unset, the server binds to
127.0.0.1 only and warns. The MCP server (stdio) is unaffected.
Incremental sync (optional)
npm run sync sync osapiens-freshdesk --since=2026-06-01T00:00:00Z --group=48000641379
Stores/refreshes raw work items only. It never creates knowledge entries (those always require your approval). Schedule it with Windows Task Scheduler.
Sources
external_id |
Routing | Write-back | Token env var | |
|---|---|---|---|---|
| Freshdesk | ticket number | group_id maps to a product |
private notes | FRESHDESK_TOKEN_<SLUG>, falling back to FRESHDESK_TOKEN |
| GitHub (issues) | owner/repo#123 |
owner/repo maps to a product, via config.repos or --group; PRs are skipped |
not supported (GitHub comments are public, so post_private_note is refused) |
GITHUB_TOKEN_<SLUG>, falling back to GITHUB_TOKEN |
Adding another source
Implement WorkItemSource (see packages/core/src/source.ts), register it in
the entrypoints, and add a source_connections row. No schema change.
Customers, versions, and controlled vocabulary
knowledge_entries is customer-blind by design. Identity never enters search
or the embedding, so retrieval matches on the fault, not on who reported it.
Customer and version are properties of the ticket, not the lesson:
work_items.customer_idis auto-matched at ingest by the requester's email domain againstcustomers.aliases(handles distributors/resellers fronting for the same account, e.g. an alias list ofdavidoff.com+arvato.comon onecustomersrow). Wrong or missing matches are corrected withset_work_item_customer; corrections are never overwritten by a later re-sync.customersstarts empty: add real ones withadd_customer.work_items.observed_versionis set manually withset_observed_versionwhen a ticket states a version. It's never inferred.knowledge_entries.resolution_patternis a controlled vocabulary, not free text: it's a foreign key intoresolution_patterns, which starts empty. Claude must calllist_resolution_patternsand pick an existing slug (or leave it unset);add_resolution_patternis a separate, deliberate action, not something invented per-ticket. This is what makes cross-team pattern queries group correctly.knowledge_entries.signals(error codes, config filenames, component names) are promoted into a real, indexed field instead of being buried instructured, so they're searchable.- Components (
list_components/add_component) are a hierarchical, per-product architecture glossary (e.g.business-objectwith nested poolsconfiguration,id-issuer, ...), fed conversationally with no ticket required. Two valid ways to populate it: you describe the app directly (call immediately), or a ticket mentions something unrecognized (Claude proposes, you confirm, then it's added). Never silently invented from a ticket.
fetch_work_item / get_context return the resolved customer_id,
customer_name, and observed_version alongside the ticket, so Claude can
reason about staleness/relevance narratively ("this customer is on v1.4, the
matched lesson was fixed in v1.6...") without any of it touching the search
index.
Semantic search
Embeddings are produced by a local model (all-MiniLM-L6-v2, 384-dim, via
fastembed). Nothing is sent to an external API. The model (~90MB) is
downloaded on first use into .fastembed-cache/. Fresh databases get the
vector column from schema.sql; to upgrade an existing database run
db/0002_pgvector.sql (and db/0003_customer_signals_patterns_components.sql
if upgrading from before customers/signals/resolution_patterns/components)
then backfill embeddings for prior entries:
npm run sync embed-backfill
Backups
npm run sync backup # pg_dump -Fc into ./backups/
npm run sync restore --file=backups/tachy-….dump # pg_restore (overwrites!)
Both need the PostgreSQL client tools (pg_dump / pg_restore) on PATH.
backups/ is git-ignored. Dumps contain real ticket data, so keep them off
any shared/synced folder. Schedule backup with Windows Task Scheduler.
Privacy
Never commit real ticket data, tokens, customer names, or internal URLs.
.env is git-ignored; tokens are resolved from env by source slug, never stored
in the DB.
License
AGPL-3.0-or-later. If you run a modified version of tachy as a network
service, you must make the modified source available to its users (see
LICENSE).
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。