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
Visit Server

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>

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured