tachy

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.

Category
访问服务器

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, the WorkItemSource interface, services (source-agnostic)
  • packages/sources/freshdesk: Freshdesk adapter
  • packages/sources/github: GitHub Issues adapter
  • packages/mcp: MCP server for Claude Code or any MCP client (the primary surface)
  • packages/api: Hono REST API (cron, teammates, future UI)
  • packages/cli: sync command
  • db/schema.sql: canonical Postgres schema

Prerequisites

  • Node 20+
  • PostgreSQL 14+ with the vector (pgvector), pg_trgm, and pgcrypto extensions available; schema.sql creates them. The easiest way to get pgvector locally is the pgvector/pgvector Docker image, or apt 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_id is auto-matched at ingest by the requester's email domain against customers.aliases (handles distributors/resellers fronting for the same account, e.g. an alias list of davidoff.com + arvato.com on one customers row). Wrong or missing matches are corrected with set_work_item_customer; corrections are never overwritten by a later re-sync. customers starts empty: add real ones with add_customer.
  • work_items.observed_version is set manually with set_observed_version when a ticket states a version. It's never inferred.
  • knowledge_entries.resolution_pattern is a controlled vocabulary, not free text: it's a foreign key into resolution_patterns, which starts empty. Claude must call list_resolution_patterns and pick an existing slug (or leave it unset); add_resolution_pattern is 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 in structured, so they're searchable.
  • Components (list_components / add_component) are a hierarchical, per-product architecture glossary (e.g. business-object with nested pools configuration, 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

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选