Civil Exam Position Filter
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Civil Exam Position FilterImport examples/positions.xlsx and filter against my saved profile."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Civil Exam Position Filter
A local, explainable shortlist for Chinese civil-service and public-institution position tables. Import a file you downloaded, save a profile, and inspect three outcomes: parsed match, needs review, or does not match. Each result retains the original columns, sheet, row, file name, and SHA-256 fingerprint.
Python 3.11+ · SQLite · CLI · MCP stdio · offline browser interface · MIT

The official announcement and recruiting authority govern eligibility. A parsed match means the supplied profile satisfies the conditions this tool recognized in the imported table. It is not an eligibility certificate. Missing profile fields, unsupported clauses, blank education/major requirements, and unmapped source columns remain visible as review requirements. The tool never crawls recruitment websites or submits applications.
Install and quick start
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e .From the repository root, run the fictional example:
cpf --config examples/config.yaml import examples/positions.xlsx --announcement 2026-10-15 --year 2026 --json
cpf --config examples/config.yaml filter --verdict ok,maybe --json
cpf --config examples/config.yaml export --format xlsx --out data/demo/shortlist.xlsx --jsonThe first command imports 14 positions from two sheets with merged title rows and headers on row 3. Repeating the same file, mapping, year, and announcement date skips all 14. Profile A yields 7 parsed matches, 2 requiring review, and 5 nonmatches. The nine shortlisted rows appear in the Excel export. Profiles B and C have independently specified expected lists in examples/expected.json; tests assert every outcome. All offices, positions, profiles, and notes are fictional. Chinese text appears only where needed to demonstrate Chinese source requirements and catalogue terms.
cpf --config examples/config.yaml serve --port 8768Open http://127.0.0.1:8768. Upload tables, edit a JSON profile, filter by outcome/location/department level/keyword, save favorites and notes, compare up to five positions, and download Excel, CSV, Markdown, or JSON reports. The interface and all assets are English and local. No CDN, telemetry, API key, account, or runtime internet access is required. Install dependencies once before offline use.
Related MCP server: JobMatch MCP
Screening rules and uncertainty
The parser recognizes explicit Chinese education/degree levels, alternatives, full-time requirements, exact majors and catalogue categories, political status, grassroots years, fresh-graduate restrictions, gender, service-project status, explicit residence/origin names, selected certificates, CET scores, named work experience, ages, and birth-date cutoffs.
Rules are deliberately narrow. Semicolons, Chinese periods, and newlines separate independent conditions. Unconsumed qualifiers and compound prose stay uncertain; no language model guesses their meaning. For example:
Source condition | Behavior |
本科及以上 | Bachelor or higher education |
仅限硕士研究生 | Master education exactly |
冶金工程 | Exact profile major name matches |
材料类 | Hierarchy match only with a confirmed applicable catalogue |
材料类及相关专业 | Recruiter confirmation required |
35周岁及以下 | Inclusive age 35 on the supplied reference date |
35周岁以下 | Age 35 requires boundary confirmation; 34 passes and 36 fails |
1990年1月1日以后出生(含) | Explicit inclusive birth-date cutoff |
35周岁以下(1990年X月X日以后出生) | Unsupported compound text; review required |
具有特殊项目认定资格 | Unsupported qualification; review required |
A definite failed condition gives no and the first failing reason; all other conditions and uncertainty remain in checks. Without a definite failure, any uncertainty gives maybe. Only otherwise is the result ok. Empty requirements are not automatically proof that the full announcement contains no restrictions. The tool does not infer age relaxations, equivalence, graduate-year eligibility, work-history classification, or residence hierarchy.
Age uses the announcement reference date you explicitly supply, not today's date. Birthday comparisons use completed years. A missing reference date gives maybe whenever age is required. A birth-date rule can be evaluated without an announcement date. Full-time status, fresh-graduate status, and grassroots years are user assertions; the tool does not verify supporting documents.
Major catalogue
<data-dir>/majors.csv is initialized from an eight-major undergraduate subset, covering selected materials and computing majors. This is not a complete catalogue, does not cover graduate disciplines, and is not assumed to govern a particular recruitment.
The checked values were read from the Ministry of Education's 2026 undergraduate catalogue, printed pages 16 and 18. Columns are code,name,category_code,category,discipline_code,discipline,level,source. Preserve codes as strings, including leading zeros. Replace the CSV or set majors to a reviewed local file. cpf majors search "计算机" searches all its fields.
By default, category matches remain maybe. Set catalogue_confirmed: true only after confirming that this catalogue and level apply to the recruitment. The fictional config enables this explicitly for its invented tables. A master's profile cannot automatically use the undergraduate hierarchy; exact major-name matches remain possible. Conflicting names/codes and missing catalogue entries require review. The catalogue is reread when the process starts; restart a running server after replacing it.
Profiles and configuration
birth_date: '1994-06-01'
education: bachelor
degree: bachelor
graduation_year: 2017
fresh: false
major_name: 冶金工程
major_code: '080404'
political: none
grassroots_years: 5
work_experience: []
household: AreaA
origin: AreaB
gender: male
certificates: []
english: {CET4: 450}
service: false
full_time: trueSave with cpf profile set profile.yaml; inspect with cpf profile check. Omitted or null attributes are unknown. Education is junior_college, bachelor, master, or doctor; degree adds none and omits junior_college. Political status is party, probationary, league, none, or democratic. Certificate identifiers currently recognized are legal and CPA. CET4/CET6 scores must be 0–710 or null. Boolean flags must be true/false/null. Geographic names are compared exactly; AreaA is distinct from Area A and no administrative hierarchy is inferred.
Configuration is YAML. --config overrides CPF_CONFIG. Data directory precedence: --data-dir → CPF_DATA_DIR → config data_dir → ./data. Config paths resolve beside the config; CLI/environment paths resolve from the working directory.
data_dir: ./data
profile: ./profile.yaml
majors: ./majors.csv
catalogue_confirmed: false
timezone: Asia/ShanghaiBackup timestamps use Asia/Shanghai. Recruitment dates are calendar dates, without timezone conversion. SQLite stores tables, positions, cached eligibility explanations, favorites, and manually imported applicant counts. Cache hashes include profile, catalogue, applicability setting, parser version, and reference date; every screening recalculates the rules. No stale cached verdict is reused. Profiles and the catalogue are separate files and included in backups.
Import mapping and provenance
Supported inputs are XLSX, XLS, and UTF-8/BOM CSV, up to 30 MB. Header detection scans the first ten rows for a position code, position title, and at least one other mapped field. Multi-sheet workbooks are supported. Merged title rows above the header are supported; the importer does not silently fill merged body qualifications. Excel formulas are not evaluated and trigger review.
mappings/guokao.yaml lists common national-table aliases. mappings/generic.yaml is an editable example with English aliases. Supply a path using --mapping mappings/custom.yaml. Each canonical field maps to a list of exact column aliases. A real workbook with both department and employing-division columns can map ambiguously; import fails with a request for a specific mapping rather than choosing one silently. Keep other text columns unmapped if their meaning is uncertain: their content enters the review reasons.
Numerically stored codes cannot recover leading zeros already lost by the spreadsheet. Original displayed values are retained as normalized strings, not Excel formatting or formulas' cached results. Blank rows and rows without codes are skipped with warnings. Source originals are not copied; the database retains all imported column values and source fingerprints.
Applicant CSV schema: code,applicants,as_of with optional table_id. Import using cpf applicants file.csv. Counts must be nonnegative integers, dates ISO 8601. The ratio is applicant count divided by vacancies; missing counts sort last and do not become zero. The as-of date remains attached. Reimport replaces manual counts for matching positions. A duplicate position code across tables requires table_id or the exact internal position ID. No live registration data is fetched.
CLI reference
Global flags work before or after commands: --data-dir, --config, --json, --quiet, and --version. Commands are noninteractive; output is a single JSON envelope even without --json. Quiet success emits nothing. --version prints the version string.
Command | Main arguments |
|
|
| Validate/save or inspect the profile |
| List imported table IDs and source fingerprints |
|
|
|
|
|
|
| One to five unambiguous codes or internal IDs |
| Update dated manual applicant counts atomically |
|
|
| Complete primary-record JSON/CSV interchange, independent of profile filtering |
| Search the selected catalogue |
| Optional |
| Loopback host only; |
| Official SDK stdio server |
Success: {"ok":true,"data":...,"warnings":[]}. Error: {"ok":false,"error":{"code":"NOT_FOUND","message":"Position not found"}}. Exit codes: 0 success, 1 I/O/database error, 2 invalid arguments/data, 3 missing record/file. Logs use stderr; MCP stdout is protocol-only.
Exports, backup, and restore
Screening exports default to ok,maybe; explicitly select ok for parsed matches only. Reports include reasons and raw source columns. JSON reports retain every rule check. CSV has a UTF-8 BOM. Excel cells are literal strings where appropriate, preventing formula evaluation of source text. CSV preserves literal source values: import text columns as text when opening untrusted data in spreadsheet software.
export --snapshot --format json writes versioned primary tables; CSV snapshots have record_type,data columns with each record serialized as JSON. Restore with import FILE --snapshot. Identical IDs are skipped; conflicting records fail and roll back. A snapshot does not include profile, catalogue, or cached explanations. Screening report JSON/CSV is distinct from portable snapshots and is not accepted as a snapshot.
A dated backup ZIP contains a consistent SQLite backup, profile if present, catalogue, and effective configuration. Stop running writers, extract a trusted backup into an empty directory, and point --data-dir there. External config paths may need adjustment; the default file names work without them. Keep the old data directory until the restored data has been checked. Back up before replacing profiles/catalogues or updating applicant counts. There is no remote authentication or encryption; use a trusted local machine.
MCP
{"mcpServers":{"positions":{"command":"/workspace/apps/civil-exam-position-filter/.venv/bin/cpf","args":["--config","/workspace/data/cpf/config.yaml","mcp"]}}}Tools: import_positions(path, mapping="guokao", announcement=null, year=null), filter_positions(profile=null, filters=null), explain_position(code, table=null), and search_majors(keyword). Import writes the database; filtering saves derived explanation cache entries. Pass a profile object to filter without changing the saved profile. Unknown conditions remain review-required in every adapter. See AGENTS.md.
Verification and limits
python -m pip install -e '.[dev]'
ruff check .
ruff format --check .
pytest -q --cov=civil_exam_position_filter --cov-report=term-missingCI runs Ubuntu Python 3.11/3.12. Tests cover over 30 condition forms, boundary dates, three complete expected lists, XLS/XLSX/CSV imports, ambiguity, atomic rollback, backup restoration, all CLI/MCP tools, a real MCP subprocess, and Web workflows. Browser acceptance covers desktop/mobile filtering, notes, comparison, profile save, upload, and downloaded exports. Validation record.
The parser does not understand every province's prose, combined age exceptions, every certificate, administrative geography, graduate catalogues, or recruitment-specific definitions. Unknown conditions require human confirmation. Large inputs are read into memory; the 30 MB compressed-file cap is not a hostile-file sandbox. This is a trusted, single-user local application with no multi-user access controls. Optional future work: reviewed province/year mappings, more explicit rule grammars, profile version history, and catalogue-specific applicability metadata.
MIT. Changelog.
Available Tools
4 toolsexplain_positionC
Explain source rules and outcomes; ambiguous codes require a table ID.
| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | ||
| table | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are supplied, so the description must carry the full disclosure burden and it largely does not: it never states that this is a read-only lookup, what happens if code is ambiguous and table is omitted (error vs. best guess), or what the explanation output contains. The only behavioral hint is the ambiguous-code/table dependency, which is minimal for a zero-annotation tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short sentence with the usage condition front-loaded and no filler. The terseness is efficient, though it borders on under-specification rather than tight writing.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With 0% schema coverage, no annotations, and no output schema, this definition should explain the domain concepts (source rules, outcomes) and the code/table relationship. It leaves the agent without enough to invoke confidently – notably what an explanation looks like and where table IDs come from.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the schema contributes no meaning for code or table. The description does add one genuinely useful semantic – table is needed specifically when code is ambiguous – but it never says what form a code or table ID takes or how to obtain a valid table ID.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
"Explain source rules and outcomes" gives a verb and a rough object, but the terms "source rules" and "outcomes" are domain jargon that never tie back to the tool name explain_position. None of the siblings (import_positions, filter_positions, search_majors) are referenced, so the agent must guess what distinguishes this from filter_positions or search_majors.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The clause "ambiguous codes require a table ID" is a conditional invocation rule, but it is about parameter preconditions rather than when to pick this tool over siblings. There is no statement of when to use it (e.g., after import_positions, or when filter_positions returns an unexpected result) and no exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
filter_positionsD
Screen parsed conditions conservatively; unknown clauses require manual review.
| Name | Required | Description | Default |
|---|---|---|---|
| filters | No | ||
| profile | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral burden. It discloses conservative screening and manual review for unknown clauses, which is useful context, but omits whether the operation is read-only, what happens to filtered items, permissions, side effects, and any rate or safety constraints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The single sentence is compact but severely under-specified rather than genuinely concise. It front-loads an action but leaves the resource and operational context unclear.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The presence of an output schema means return values need not be explained, but the description still fails to cover the two optional object parameters and the tool's filtering behavior. For a tool with no annotations and vague input schema, more context is needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% description coverage and the description never mentions the two parameters, filters and profile. It adds no meaning about their expected shape or purpose beyond the bare schema titles.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description says "Screen parsed conditions conservatively" but never identifies the resource being filtered (positions) or what kinds of conditions are screened. It does not clearly distinguish the tool from siblings such as explain_position, import_positions, or search_majors.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states a rule for unknown clauses requiring manual review, but gives no guidance on when to use this tool instead of the sibling tools. There is no context about prerequisites, expected inputs, or alternative workflows.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
import_positionsC
Import a local user-supplied table. Writes SQLite; identical imports are skipped.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| year | No | ||
| mapping | No | guokao | |
| announcement | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It usefully discloses that the operation writes SQLite and that identical imports are skipped, but it omits overwrite/merge semantics, what counts as identical, permission requirements, and failure behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two short, front-loaded sentences with no wasted wording. Its terseness is efficient, though it also contributes to the missing behavioral and parameter detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a four-parameter mutation tool with no annotations, no output schema, and 0% schema description coverage, this is significantly underspecified. An agent would not know the expected file format, mapping semantics, or import side effects beyond the bare fact that SQLite is written.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% across four parameters, so the description must compensate but does not. It never explains what path refers to, how year is used, what the mapping values mean, or what announcement does.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action and resource: importing a local user-supplied table into SQLite. It is clear that this is a write/import operation, which contrasts with the read-oriented sibling tools, though it does not explicitly name those siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when this tool should be used instead of the query/filter/explain siblings, nor are prerequisites or supported import scenarios described. The only usage-adjacent information is that identical imports are skipped.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_majorsC
Search the local replaceable catalogue subset; no network request.
| Name | Required | Description | Default |
|---|---|---|---|
| keyword | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are supplied, so the description carries the full behavioral burden. It does disclose one useful trait — that the query runs against a local catalogue with no network request — but says nothing about read-only nature, failure behavior on unmatched keywords, or result scoping; the output schema covers return shape.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is a single short sentence with no filler, and the local-execution note is front-loaded. Brevity here is efficient rather than padded, though it crosses into under-specification rather than crispness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is structurally simple (one required parameter, output schema present), so the bar is low and returns need not be explained. Still, the description leaves the core subject matter and the keyword's matching behavior unexplained, which is insufficient even for a simple search.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and the single required parameter "keyword" is completely undocumented in both schema and description. The description adds no matching semantics (substring vs exact, case sensitivity, empty-string handling) that an agent would need to call it correctly.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
"Search" merely restates the verb already in the name, and the resource is described only as a "local replaceable catalogue subset", which is cryptic jargon. Nothing in the description explains what a "major" is or how this differs from the position-oriented siblings (filter_positions, explain_position).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no statement of when to use this tool versus filter_positions or explain_position, despite obvious overlap with a filtering tool. The only hint is "no network request", which implies an offline path but gives no routing rule or prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.0- First observed
explain_position - First observed
filter_positions - First observed
import_positions - First observed
search_majors
TDQS
Scored across 4 tools
Each tool maps to a distinct stage of the workflow: import (load data), search_majors (catalogue lookup), filter_positions (screening), and explain_position (rule/outcome explanation). The boundary between filter_positions and explain_position is the only mild overlap, since both deal with parsed conditions, but the descriptions distinguish screening from explaining.
All four tools follow the same verb_noun snake_case pattern: explain_position, import_positions, filter_positions, search_majors. Minor singular/plural variation (position vs positions) exists but the convention is clearly consistent.
Four tools is a tight, well-scoped set for a position-filtering server: one per core operation (import, search, filter, explain). Nothing appears redundant or missing in terms of count.
The core lifecycle is covered: import source data, search the catalogue, filter positions, and explain outcomes. Minor gaps exist around managing imported tables (no list/delete/refresh operation) and no export of filtered results, but agents can work around these.
Maintenance
Related MCP Connectors
Remote MCP server for China brand visibility, destination demand, and KOL discovery workflows.
China company tools via MCP; separate HTTP x402 screening reports and QCC qualifications.
Sanctions Screening MCP — screen people, companies, vessels, and aircraft against the US…
Structured, sourced data on China visa-free entry rules and Chinese regulatory changes, served as a remote MCP server for agents.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceMCP server for China-to-Indonesia outbound intelligence ingestion, enabling source discovery, candidate review, event confirmation, taxonomy management, stats analysis, fact-checking, and CSV export.-
- FlicenseNot gradedqualityCmaintenanceEnables AI-powered job search and resume matching with strict skill verification, resume parsing, and configurable user preferences, plus MCP tools for job search, Excel export, and email dispatch.-
- AlicenseNot gradedqualityCmaintenanceEnables due diligence and KYC by querying PEPs, national and international sanctions, debarment lists, arrest warrants, criminal records, leniency agreements, and forced labor registries via hosted, prepaid read-only MCP tools.MIT
- AlicenseAqualityBmaintenanceEnables users to import, query, edit, audit, and export local personal records through schema-driven MCP tools while masking sensitive fields by default.8MIT