Germany Intelligence MCP

Germany Intelligence MCP

An evidence-based career intelligence layer for the German software job market, exposed to Claude as an MCP server.

Category
访问服务器

README

<div align="center">

🇩🇪 Germany Intelligence MCP

An evidence-based career intelligence layer for the German software job market, exposed to Claude as an MCP server.

CI Node TypeScript Tests License Status

Not a job search tool. A decision engine that answers one question every morning:

“What should I learn today to maximise my chances of getting a software engineering job in Germany?”

</div>


⚠️ Read this before using it

v1.0 Core is infrastructure, not a finished product. Its own QA pass graded it C — Needs Another Stabilization Sprint, and that assessment is published in full:

Works Ingestion, deduplication, storage, trend analysis, full-text search, metrics, health, MCP tool surface
⚠️ Known-wrong German language detection has 50% recall. Extraction misses hyphenated German compounds in 24% of postings
Not implemented 4 of 5 recommendation surfaces are stubs. The 5th would tell a C1 English speaker to learn English

👉 docs/KNOWN-ISSUES.md — every defect, measured, reproducible, and pinned by a test.

This honesty is deliberate. The project's core promise is that no claim is made without evidence, and that has to apply to claims about the project itself.


The idea

Most job tools optimise for finding postings. This one assumes finding jobs is easy and knowing what to learn is hard. It treats the job market as a dataset and asks what the evidence actually says.

                          ┌──────────────────────────────┐
                          │   Claude (reasoning layer)   │
                          └───────────────┬──────────────┘
                                          │ MCP / JSON-RPC over stdio
    ┌─────────────────────────────────────▼─────────────────────────────────────┐
    │  L5  MCP TOOL SURFACE       10 coarse tools · Zod-validated · evidence     │
    ├───────────────────────────────────────────────────────────────────────────┤
    │  L4  ENGINES                trend · essentialism · gap · match · learning  │
    ├───────────────────────────────────────────────────────────────────────────┤
    │  L3  KNOWLEDGE STORE        SQLite + FTS5 · repositories own all SQL       │
    ├───────────────────────────────────────────────────────────────────────────┤
    │  L2  ANALYSIS PIPELINE      normalise → extract → classify → dedupe        │
    ├───────────────────────────────────────────────────────────────────────────┤
    │  L1  SOURCE ADAPTERS        anti-corruption layer · one interface, N APIs  │
    └───────────────────────────────────────────────────────────────────────────┘

Dependency rule: arrows point downward only. An engine never imports a source adapter; a source adapter never imports the database.

What "evidence-based" means here

No number is emitted without its denominator. This is enforced by the type system and the database schema, not by convention:

interface Evidence {
  subject: string;         // "ops.docker"
  frequency: number;       // 0.63
  observedIn: number;      // 214   ← the numerator
  totalAnalysed: number;   // 339   ← THE DENOMINATOR
  windowDays: number;      // 14
  sampleJobIds: string[];  // openable, verifiable
  meanConfidence: number;  // how good was the extraction behind this
  lowConfidence: boolean;  // sample too small to act on
}

recommendation.evidence is NOT NULL. pickTodayFocus() cannot compile without threading evidence through. A recommendation you cannot defend is unrepresentable.


Quick start

git clone https://github.com/mohitakki/germany-intelligence-mcp.git
cd germany-intelligence-mcp

npm ci                                              # no native build, no compiler
cp .env.example .env
cp config/profile.example.json config/profile.json  # then edit your skill levels

npm test          # 106 tests, ~40 against real SQLite
npm run ingest    # live fetch from the German market
npm run health    # is the data trustworthy right now?

No native dependencies. Storage is node:sqlite behind a driver port — see ADR-006 for why that decision and a production bug were the same decision.

<details> <summary><b>Wire it into Claude Desktop</b></summary>

npm run build

claude_desktop_config.json:

{
  "mcpServers": {
    "germany-intelligence": {
      "command": "node",
      "args": ["--no-warnings", "/absolute/path/to/germany-intelligence-mcp/dist/index.js"],
      "env": {
        "GIM_DATA_DIR": "/absolute/path/to/germany-intelligence-mcp/data",
        "GIM_PROFILE_PATH": "/absolute/path/to/germany-intelligence-mcp/config/profile.json"
      }
    }
  }
}

Restart Claude, then ask: "Run my daily briefing." </details>


MCP tools

Tool Status What it does
health Database + FTS, providers, last ingest age, extraction quality, jobs indexed. Call this first if anything looks odd.
refresh_market Fetch, normalise, dedupe, store. Returns full run metrics.
get_market_trends Technology demand with counts, frequencies, confidence and sample job ids.
get_skill_gap Your profile vs the market, bucketed and ranked, each item evidenced.
search_jobs BM25 full-text + filters. One row per deduplicated vacancy.
list_sources Providers, config state, legal status, compliance notes.
mark_progress Records learning so a skill stops being recommended.
get_daily_briefing Market summary + trends + gaps. Recommendation sections are empty.
get_today_focus Throws — Phase 6.
generate_interview_questions · analyse_resume Return [] — Phase 7/8.

Ten coarse tools, not thirty. Every description is loaded into Claude's context on every turn, so tool sprawl directly degrades reasoning quality.


Data sources

Only sources that permit programmatic access are shipped.

Source Status Coverage
Bundesagentur für Arbeit ⚠️ implemented, never network-tested Germany's largest job database (~1M postings). Official federal API, public client id, no registration.
Arbeitnow ✅ verified end-to-end Public board API, no key. Berlin/Munich tech, English-language — the visa-sponsoring startup segment.
ATS boards (Greenhouse, Lever, Ashby, Personio, Recruitee) 🔲 Phase 3 Canonical postings from the companies you actually want. Highest-value next build.
src/sources/custom/ 🔲 slot Your own adapters, disabled by default, complianceNote required.

<details> <summary><b>Why no LinkedIn / StepStone / Indeed / Xing</b></summary>

None offer an open job-search API. All four prohibit automated access in their Terms of Service and enforce it technically. No adapter ships for them and none should be added — an IP ban is the mild outcome; a legal notice while applying for a German work visa is the bad one.

src/sources/custom/ exists for sources you have a legitimate route to: company career feeds, boards with a documented API, partner feeds, manual CSV import.

See ADR-001. </details>


How a recommendation earns its place

Five gates, all of which must pass:

  1. Sample floor — ≥ 20 deduplicated jobs analysed
  2. Evidence floor — the skill appears in ≥ 5 postings
  3. Relevance floor — ≥ 10% of jobs request it
  4. Deficit — your self-assessed level is below 4/5
  5. Cooldown — not recommended in the last 21 days

Survivors are ranked:

priority = marketFrequency × skillDeficit × momentum

Multiplicative, not additive — so a skill you already have scores ≈ 0 no matter how in-demand it is. That is the mechanism that stops the system telling you to learn React for the fortieth time.


The engineering story

This repo went through four adversarial cycles after "done". Each one is documented, because the findings are more interesting than the code.

🔴 The write path was dead and the run said ok

JobRepository.upsert ended with INSERT INTO job_fts (...) ON CONFLICT DO NOTHING. SQLite rejects UPSERT against a virtual table. Every insert threw, runIngest caught it per-job, and the run reported success having stored nothing.

The schema had been "verified" by running a hand-written query against hand-inserted rows — never the repository's own statement. A test that doesn't cross the boundary tests nothing.ADR-006

🔴 A Berlin train line was inflating AWS demand

Eight of twelve ordinary German sentences produced phantom skill demand:

Sentence (verbatim from real postings) Phantom skill
"no less than three years of experience" CSS/Less
"the position starts in spring 2027" Spring Boot
"gut erreichbar mit der S3 und der U2" AWS ← an S-Bahn line
"you react quickly to incidents" React

The defect wasn't the aliases — it was the default. A token was assumed technical unless something stopped it. Now ambiguous aliases are assumed non-technical unless a requirement marker or a confirmed technology list proves otherwise. → ADR-003

🔴 The dedup threshold was picked, not measured

Version one used Hamming ≤ 3 because it "felt safely conservative." It matched nothing but byte-identical text.

 0 bits  identical                    11 bits  ~15% extra boilerplate
 5 bits  hyphenation edit             13 bits  bullets reordered
 9 bits  one extra sentence           31 bits  a completely different job

Real variants cluster at 5–13; unrelated documents sit past 30. 12 sits in a wide, empty gap.ADR-002

🔴 The extractor was accurate; the data was wrong

Ground truth over 111 real postings: 96.6% recall, 1 false positive in 59 mentions. The extractor is fine. The corpus genuinely contains almost no React/TypeScript work — TypeScript appears in 3 of 111 postings.

Diagnosing this as a source selection problem rather than an extraction problem is the difference between fixing it and tuning the wrong knob.


Documentation

Document What it is
KNOWN-ISSUES.md Every measured defect with a reproduction. Read before trusting output.
RUNBOOK.md Daily operation, metric interpretation, backup, replay, troubleshooting
ARCHITECTURE.md Layers, data flow, dependency rules, trade-offs
ROADMAP.md Build order with reasoning, ~35 h to complete
docs/ADR/ 7 decision records — why, with rejected alternatives

<details> <summary><b>Architecture Decision Records</b></summary>

ADR Decision
001 Provider architecture and the anti-corruption layer
002 SimHash deduplication, blocking, measured threshold
003 Curated taxonomy + context-window gating
004 Untrusted-content trust boundary
005 LLM as additive enrichment only, never authoritative
006 SQLite via node:sqlite behind a driver port
007 Evidence as a required type

</details>


Testing

106 tests. ~40 cross into real SQLite. Three suites, three jobs:

npm test                                   # everything
npx tsx --test tests/regressions.test.ts   # one block per shipped bug
npx tsx --test tests/corpus.test.ts        # 111 real postings, pinned baselines
  • extraction.test.ts — pure logic: German gender markers, company suffixes, SimHash, the essentialism filter
  • regressions.test.ts — one describe per critical bug, with the original defect in the comment so nobody "fixes" the test later
  • ingest.test.ts — pipeline → repository → SQLite, error isolation, migration idempotency
  • corpus.test.ts — 111 real German postings. Pins the measured baselines and the known defects. When you fix one, its test fails — that failure is the proof.

The corpus is committed so every number in the docs is reproducible.


Security

  • Prompt injection — job text is attacker-controlled. Nonce-delimited envelope built before the LLM path exists; detection counts, never filters (ADR-004)
  • Credential redaction — no query string ever reaches a log line, enforced at the boundary and again centrally
  • SQL injection — fully parameterised; verified against '); DROP TABLE job;--
  • Adversarial robustness — 27 hostile inputs (500 KB descriptions, malformed HTML, emoji, RTL, null bytes, FTS syntax), zero crashes

Contributing

Personal project, but the patterns are reusable. If you fork it:

  1. Read ADR-003 before touching the taxonomy — several constraints look arbitrary and are load-bearing
  2. npm test must stay green; the false-positive corpus is the guard rail
  3. New decisions get a new ADR — template. ADRs are immutable once Accepted; supersede, don't edit

<div align="center">

MIT · Built as a systems-engineering exercise in evidence-based recommendation. Not affiliated with Bundesagentur für Arbeit or Arbeitnow.

</div>

推荐服务器

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 模型以安全和受控的方式获取实时的网络信息。

官方
精选