salary-mcp-agent

salary-mcp-agent

MCP server exposing Taiwan's MOPS salary data (2019-2025) via four read-only tools, enabling LLMs to query company salaries, industry stats, and trends under strict constraints.

Category
访问服务器

README

salary-mcp-agent

An MCP server that exposes a real dataset to a language model under explicit constraints, and a Claude Agent SDK agent that researches through it.

The dataset is Taiwan's MOPS non-managerial salary disclosures for listed companies, 2019–2025 — public company-level aggregates from the TWSE and TPEx open-data endpoints.

MCP server salary_mcp/ — four read-only tools + a schema resource, stdio transport
Agent agent/researcher.py — Claude Agent SDK, multi-step planning over those tools
Tests 29 passing, including 16 that drive the server over the real protocol

In brief

A model that can query a database is easy. A model that can query a database and cannot do anything else takes some design. This repo is mostly about the second problem: what the tool surface should look like when the caller is a language model, and how to prove the constraints hold.

Quick start

uv venv && uv pip install -e ".[dev]"
.venv/bin/python -m pytest          # 29 passed

Run the server on its own (it speaks MCP over stdio, so it waits for a client):

.venv/bin/python -m salary_mcp.server

Run the agent (needs ANTHROPIC_API_KEY):

.venv/bin/python -m agent.researcher "崇越科技 (5434) 的薪資水準在同業裡算好嗎?"

The tool surface

Tool Answers
lookup_company(query) One company by stock code or name substring
industry_stats(industry) Median, p25/p75, and range across a sector
top_by_median(industry, min_median, limit) Ranked list under filters
company_trend(code) One company's median for each year on record

Plus a salary://schema resource describing the fields, units, and the two reading rules that matter (below).

Design decisions

No free-form query tool. There is deliberately no run_query, no SQL passthrough, no eval. Every question the model can ask is a named tool with a typed signature, so the reachable query space is those four functions and nothing else. A generic query tool would hand the model — and anything that can prompt-inject it — the full expressive power of the query language. This is the decision the rest of the design follows from.

Read-only by construction. No write, delete, or exec tool exists. Only dataset.py touches the filesystem, and only inside data/.

Arguments are validated before use, and clamped server-side. Stock codes must match ^\d{4,6}$; free text is length-capped; limit is clamped to 50 in the server rather than trusted to the caller. Path traversal and injection-shaped strings come back as a short validation message — never a traceback, never a filesystem path.

Two independent gates. The agent checks each call against an allow-list before it runs, and the server validates arguments again on arrival. A mistake in either one alone is not sufficient to reach the data.

Explicit ceilings. max_turns and max_budget_usd are set rather than left to default. An agent that chooses its own next step needs a limit that does not depend on it choosing to stop.

Every tool call is audited. ToolAudit records what the agent reached for, allowed and denied. A transcript shows what an agent said; only the audit shows what it tried.

Two rules the data forces on the answer

These are in the system prompt because getting them wrong produces confident, wrong numbers:

  • Quote the median, not the mean. When median/mean falls below about 0.85 the distribution is right-skewed — the mean is being pulled up by a few high earners. The server reports the ratio and flags it.
  • A missing year is missing, not zero. A company absent from a year was below the disclosure threshold or not yet listed. company_trend returns null for those years and names them; rendering them as zero would invent a pay cut that never happened. 台灣虎航 (6757) is the worked example — listed part-way through the window, and there is a test asserting its gap years never render as 0.0.

Verification status

Being precise about what is and isn't covered:

Status
MCP protocol — handshake, tool discovery, tool calls, resource reads ✅ 16 tests against a real server subprocess over stdio
Argument validation, clamping, traversal and injection rejection ✅ Covered, including the paths that must fail
Security posture (no mutating or free-form tool is exposed) ✅ Asserted, and the assertion is self-validated (below)
Agent permission gate, allow-list, audit, ceilings ✅ Unit-tested, both outcomes
Agent allow-list matches the tools the server actually serves ✅ Verified by launching the real server from the agent's own config
The agent loop itself — SDK driving a model through the tools Not covered. Requires API credentials, which are not present in the environment this was built in. The code follows the documented SDK API and constructs against the real SDK types, but no live multi-turn run has been executed.

The posture assertions are self-validated. Guard tests that only ever pass are the ones you should trust least, so the check was verified against a known-bad input: injecting a run_query tool into the server makes three tests fail (test_no_mutating_tool_is_exposed, test_handshake_and_tool_discovery, test_every_allow_listed_tool_exists_on_the_real_server). Removing it returns them to green. The first attempt at that probe appended the tool after mcp.run(), where it never executed — so the tests passed and briefly looked broken. Worth stating because the failure mode is general: a guard test verified with a probe that isn't actually bad tells you nothing.

Data

data/salary-{2019..2025}.json — snapshots of the TWSE and TPEx t187ap46 open-data endpoints. Public, company-level aggregates; no personal data. Figures are 萬元 (NT$10,000) per year.

Two upstream quirks the loader handles: six 2025 rows carry a null industry, and companies enter and leave the dataset as they cross the disclosure threshold.

Layout

salary_mcp/dataset.py   loading, validation, queries — the only filesystem access
salary_mcp/server.py    MCP tools and the schema resource
agent/researcher.py     Agent SDK options, permission gate, tool audit
tests/                  16 protocol tests, 13 agent-guardrail tests

Licence

MIT.

推荐服务器

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

官方
精选