mcp-il-professionals 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_il_professionals-0.1.0/.dockerignore +6 -0
- mcp_il_professionals-0.1.0/.github/workflows/publish.yml +49 -0
- mcp_il_professionals-0.1.0/.gitignore +9 -0
- mcp_il_professionals-0.1.0/.mcp.json +5 -0
- mcp_il_professionals-0.1.0/CLAUDE.md +26 -0
- mcp_il_professionals-0.1.0/Dockerfile +9 -0
- mcp_il_professionals-0.1.0/LICENSE +21 -0
- mcp_il_professionals-0.1.0/MEMORY.md +14 -0
- mcp_il_professionals-0.1.0/PKG-INFO +111 -0
- mcp_il_professionals-0.1.0/README.md +94 -0
- mcp_il_professionals-0.1.0/pyproject.toml +42 -0
- mcp_il_professionals-0.1.0/server.json +21 -0
- mcp_il_professionals-0.1.0/server.py +7 -0
- mcp_il_professionals-0.1.0/src/mcp_il_professionals/__init__.py +1 -0
- mcp_il_professionals-0.1.0/src/mcp_il_professionals/ckan.py +81 -0
- mcp_il_professionals-0.1.0/src/mcp_il_professionals/server.py +250 -0
- mcp_il_professionals-0.1.0/tests/test_server.py +50 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Publish to PyPI and MCP Registry
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.12"
|
|
16
|
+
- run: pip install -e ".[dev]"
|
|
17
|
+
- run: ruff check .
|
|
18
|
+
- run: pytest
|
|
19
|
+
|
|
20
|
+
publish:
|
|
21
|
+
needs: test
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
environment: pypi
|
|
24
|
+
permissions:
|
|
25
|
+
id-token: write # PyPI trusted publishing + MCP Registry GitHub OIDC
|
|
26
|
+
contents: read
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: "3.12"
|
|
32
|
+
- name: Check tag matches versions
|
|
33
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
34
|
+
run: |
|
|
35
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
36
|
+
PY=$(python -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
37
|
+
SJ=$(python -c "import json;print(json.load(open('server.json'))['version'])")
|
|
38
|
+
test "$TAG" = "$PY" && test "$TAG" = "$SJ" || { echo "tag=$TAG pyproject=$PY server.json=$SJ"; exit 1; }
|
|
39
|
+
- run: pip install build && python -m build
|
|
40
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
41
|
+
- name: Install mcp-publisher
|
|
42
|
+
run: |
|
|
43
|
+
curl -sL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher
|
|
44
|
+
- name: Wait for PyPI to serve the new version
|
|
45
|
+
run: sleep 60
|
|
46
|
+
- name: Publish to MCP Registry
|
|
47
|
+
run: |
|
|
48
|
+
./mcp-publisher login github-oidc
|
|
49
|
+
./mcp-publisher publish
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# mcp-il-professionals — CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Verify licensed professionals in Israel for AI agents: real-estate brokers, registered contractors, appraisers, CPAs, surveyors, private investigators — live from data.gov.il.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
Python 3.11 · FastMCP · httpx · pytest. No database: every tool queries data.gov.il CKAN `datastore_search` live.
|
|
7
|
+
|
|
8
|
+
## Layout
|
|
9
|
+
- server.py — Prefect Horizon entrypoint (`server.py:mcp`); check with `fastmcp inspect server.py:mcp`
|
|
10
|
+
- src/mcp_il_professionals/server.py — tools + resource IDs (constants `R_*` at top)
|
|
11
|
+
- src/mcp_il_professionals/ckan.py — shared CKAN client (retries, User-Agent, `~`→`"` cleanup, `meta()` provenance). Keep identical across mcp-il-* repos.
|
|
12
|
+
- tests/ — offline tests (monkeypatched `datastore_search`); `pytest -m live` for real calls
|
|
13
|
+
|
|
14
|
+
## Rules
|
|
15
|
+
- Every tool response includes `source`, `retrieved_at`, `attribution` via `meta()`.
|
|
16
|
+
- Resource IDs can change when a ministry republishes — if a tool returns 404/empty, re-resolve with
|
|
17
|
+
`package_search` and update the `R_*` constant.
|
|
18
|
+
- Exact matches via `filters` (numbers must be int unless the dataset stores strings); free text via `q`.
|
|
19
|
+
- Never store or log personal queries. Data is public registry data only.
|
|
20
|
+
- Update MEMORY.md at the end of every session.
|
|
21
|
+
|
|
22
|
+
## Model routing
|
|
23
|
+
Architect/Advisor: Fable (else Opus) · Orchestrator: Opus · Executor: Sonnet · Explorer/tests: Haiku · tiny text jobs: Jetson llama.cpp · graphics: Gemini/ChatGPT.
|
|
24
|
+
|
|
25
|
+
## Deployment
|
|
26
|
+
Prefect Horizon free tier (public HTTP), entrypoint `server.py:mcp`. data.gov.il is reachable from cloud hosts.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oleg Sher
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# mcp-il-professionals — MEMORY.md
|
|
2
|
+
|
|
3
|
+
## Status (2026-11-14)
|
|
4
|
+
- v0.1.0 built and tested against live data.gov.il; repo https://github.com/olegsher-ds-2025/mcp-il-professionals
|
|
5
|
+
- PyPI + MCP Registry publishing via GitHub Actions on tag v*; Horizon free tier is private-only (public needs paid plan) → self-host on laptop 10.0.0.100 via Cloudflare Tunnel (mcp-il-deploy)
|
|
6
|
+
|
|
7
|
+
## Next tasks
|
|
8
|
+
1. Deploy to Prefect Horizon; add hosted URL to README
|
|
9
|
+
2. List on MCP Registry, Glama, Smithery, agentskills.co.il
|
|
10
|
+
3. Roadmap items in README
|
|
11
|
+
|
|
12
|
+
## Facts
|
|
13
|
+
- data.gov.il CKAN reachable from Claude cloud workspace and cloud hosts (unlike cbs.gov.il)
|
|
14
|
+
- Registry text encodes `"` as `~` — cleaned in ckan.clean_value
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mcp-il-professionals
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Verify licensed professionals in Israel for AI agents: real-estate brokers, registered contractors, appraisers, CPAs, surveyors, private investigators — live from data.gov.il
|
|
5
|
+
Project-URL: Repository, https://github.com/olegsher-ds-2025/mcp-il-professionals
|
|
6
|
+
Author: Oleg Sher
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: data.gov.il,israel,mcp,model-context-protocol,open-data
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: fastmcp>=2.0
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# mcp-il-professionals
|
|
19
|
+
|
|
20
|
+
<!-- mcp-name: io.github.olegsher-ds-2025/mcp-il-professionals -->
|
|
21
|
+
|
|
22
|
+
Verify licensed professionals in Israel for AI agents: real-estate brokers, registered contractors, appraisers, CPAs, surveyors, private investigators — live from data.gov.il.
|
|
23
|
+
|
|
24
|
+
**אימות בעלי מקצוע מורשים: מתווכים, קבלנים רשומים, שמאים, רואי חשבון, מודדים, חוקרים פרטיים**
|
|
25
|
+
|
|
26
|
+
An [MCP](https://modelcontextprotocol.io) server that lets Claude and other AI assistants query official Israeli
|
|
27
|
+
government open data in plain language. No API key, no scraping — every call goes to the public
|
|
28
|
+
[data.gov.il](https://data.gov.il) CKAN API and returns the source with each answer.
|
|
29
|
+
|
|
30
|
+
## Tools
|
|
31
|
+
|
|
32
|
+
| Tool | What it does |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `list_professions` | Which registries are available, and how to verify electricians / engineers / lawyers |
|
|
35
|
+
| `verify_professional` | Check a licence by name or number in one registry |
|
|
36
|
+
| `find_contractors` | Registered contractors by city, branch (שיפוצים, בניה, חשמלאות…), classification |
|
|
37
|
+
| `contractor_profile` | All branches & classifications of one contractor |
|
|
38
|
+
| `search_all_registries` | One name across every registry |
|
|
39
|
+
| `health` | Connectivity check |
|
|
40
|
+
|
|
41
|
+
## Try asking
|
|
42
|
+
|
|
43
|
+
- “Is the real-estate broker דורית גריצר licensed?”
|
|
44
|
+
- “Find registered renovation contractors in Modiin recognized for government work.”
|
|
45
|
+
- “Check CPA licence 5005603.”
|
|
46
|
+
- “What licences does this person hold across all registries?”
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
No install needed with [uv](https://docs.astral.sh/uv/):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uvx mcp-il-professionals
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Claude Desktop / Claude Code (`.mcp.json`):
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{ "mcpServers": { "il-professionals": { "command": "uvx", "args": ["mcp-il-professionals"] } } }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or `pip install mcp-il-professionals`, or from source: `pip install git+https://github.com/olegsher-ds-2025/mcp-il-professionals`.
|
|
63
|
+
|
|
64
|
+
Also listed in the official MCP Registry as `io.github.olegsher-ds-2025/mcp-il-professionals`.
|
|
65
|
+
|
|
66
|
+
Run over HTTP (for remote hosting):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
MCP_TRANSPORT=http PORT=8000 mcp-il-professionals # → http://localhost:8000/mcp
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Docker: `docker build -t mcp-il-professionals . && docker run -p 8000:8000 mcp-il-professionals`.
|
|
73
|
+
|
|
74
|
+
Prefect Horizon entrypoint: `server.py:mcp`.
|
|
75
|
+
|
|
76
|
+
## Data sources
|
|
77
|
+
|
|
78
|
+
- Licensed real-estate brokers (Ministry of Justice)
|
|
79
|
+
- Registered contractors (Registrar of Contractors)
|
|
80
|
+
- Real-estate appraisers
|
|
81
|
+
- Certified public accountants (CPA Council)
|
|
82
|
+
- Vehicle appraisers (Ministry of Transport)
|
|
83
|
+
- Licensed surveyors (Survey of Israel)
|
|
84
|
+
- Private investigators & investigation firms
|
|
85
|
+
|
|
86
|
+
All data © the publishing Israeli government authority, via data.gov.il. This project is independent and not
|
|
87
|
+
affiliated with the Government of Israel. Registry data can lag; verify critical facts with the authority.
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pip install -e ".[dev]"
|
|
93
|
+
pytest # offline tests
|
|
94
|
+
pytest -m live # hits data.gov.il
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Roadmap
|
|
98
|
+
|
|
99
|
+
- [ ] Electricians licence search (Ministry of Labor — not open data; investigate official lookup)
|
|
100
|
+
- [ ] Engineers & architects registry
|
|
101
|
+
- [ ] Insurance agents (Capital Market Authority)
|
|
102
|
+
- [ ] Join contractors with mcp-il-companies due_diligence
|
|
103
|
+
|
|
104
|
+
## Related
|
|
105
|
+
|
|
106
|
+
Part of a family of Israeli public-data MCP servers by [Oleg Sher](https://linkedin.com/in/sheroleg):
|
|
107
|
+
mcp-il-companies · mcp-il-vehicles · mcp-il-health · mcp-il-professionals · mcp-cbs-places · mcp-mavat.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# mcp-il-professionals
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.olegsher-ds-2025/mcp-il-professionals -->
|
|
4
|
+
|
|
5
|
+
Verify licensed professionals in Israel for AI agents: real-estate brokers, registered contractors, appraisers, CPAs, surveyors, private investigators — live from data.gov.il.
|
|
6
|
+
|
|
7
|
+
**אימות בעלי מקצוע מורשים: מתווכים, קבלנים רשומים, שמאים, רואי חשבון, מודדים, חוקרים פרטיים**
|
|
8
|
+
|
|
9
|
+
An [MCP](https://modelcontextprotocol.io) server that lets Claude and other AI assistants query official Israeli
|
|
10
|
+
government open data in plain language. No API key, no scraping — every call goes to the public
|
|
11
|
+
[data.gov.il](https://data.gov.il) CKAN API and returns the source with each answer.
|
|
12
|
+
|
|
13
|
+
## Tools
|
|
14
|
+
|
|
15
|
+
| Tool | What it does |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `list_professions` | Which registries are available, and how to verify electricians / engineers / lawyers |
|
|
18
|
+
| `verify_professional` | Check a licence by name or number in one registry |
|
|
19
|
+
| `find_contractors` | Registered contractors by city, branch (שיפוצים, בניה, חשמלאות…), classification |
|
|
20
|
+
| `contractor_profile` | All branches & classifications of one contractor |
|
|
21
|
+
| `search_all_registries` | One name across every registry |
|
|
22
|
+
| `health` | Connectivity check |
|
|
23
|
+
|
|
24
|
+
## Try asking
|
|
25
|
+
|
|
26
|
+
- “Is the real-estate broker דורית גריצר licensed?”
|
|
27
|
+
- “Find registered renovation contractors in Modiin recognized for government work.”
|
|
28
|
+
- “Check CPA licence 5005603.”
|
|
29
|
+
- “What licences does this person hold across all registries?”
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
No install needed with [uv](https://docs.astral.sh/uv/):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uvx mcp-il-professionals
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Claude Desktop / Claude Code (`.mcp.json`):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{ "mcpServers": { "il-professionals": { "command": "uvx", "args": ["mcp-il-professionals"] } } }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or `pip install mcp-il-professionals`, or from source: `pip install git+https://github.com/olegsher-ds-2025/mcp-il-professionals`.
|
|
46
|
+
|
|
47
|
+
Also listed in the official MCP Registry as `io.github.olegsher-ds-2025/mcp-il-professionals`.
|
|
48
|
+
|
|
49
|
+
Run over HTTP (for remote hosting):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
MCP_TRANSPORT=http PORT=8000 mcp-il-professionals # → http://localhost:8000/mcp
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Docker: `docker build -t mcp-il-professionals . && docker run -p 8000:8000 mcp-il-professionals`.
|
|
56
|
+
|
|
57
|
+
Prefect Horizon entrypoint: `server.py:mcp`.
|
|
58
|
+
|
|
59
|
+
## Data sources
|
|
60
|
+
|
|
61
|
+
- Licensed real-estate brokers (Ministry of Justice)
|
|
62
|
+
- Registered contractors (Registrar of Contractors)
|
|
63
|
+
- Real-estate appraisers
|
|
64
|
+
- Certified public accountants (CPA Council)
|
|
65
|
+
- Vehicle appraisers (Ministry of Transport)
|
|
66
|
+
- Licensed surveyors (Survey of Israel)
|
|
67
|
+
- Private investigators & investigation firms
|
|
68
|
+
|
|
69
|
+
All data © the publishing Israeli government authority, via data.gov.il. This project is independent and not
|
|
70
|
+
affiliated with the Government of Israel. Registry data can lag; verify critical facts with the authority.
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install -e ".[dev]"
|
|
76
|
+
pytest # offline tests
|
|
77
|
+
pytest -m live # hits data.gov.il
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Roadmap
|
|
81
|
+
|
|
82
|
+
- [ ] Electricians licence search (Ministry of Labor — not open data; investigate official lookup)
|
|
83
|
+
- [ ] Engineers & architects registry
|
|
84
|
+
- [ ] Insurance agents (Capital Market Authority)
|
|
85
|
+
- [ ] Join contractors with mcp-il-companies due_diligence
|
|
86
|
+
|
|
87
|
+
## Related
|
|
88
|
+
|
|
89
|
+
Part of a family of Israeli public-data MCP servers by [Oleg Sher](https://linkedin.com/in/sheroleg):
|
|
90
|
+
mcp-il-companies · mcp-il-vehicles · mcp-il-health · mcp-il-professionals · mcp-cbs-places · mcp-mavat.
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mcp-il-professionals"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Verify licensed professionals in Israel for AI agents: real-estate brokers, registered contractors, appraisers, CPAs, surveyors, private investigators — live from data.gov.il"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Oleg Sher" }]
|
|
13
|
+
keywords = ["mcp", "israel", "open-data", "data.gov.il", "model-context-protocol"]
|
|
14
|
+
dependencies = ["fastmcp>=2.0", "httpx>=0.27"]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
dev = ["pytest>=8", "ruff>=0.5"]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
mcp-il-professionals = "mcp_il_professionals.server:main"
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Repository = "https://github.com/olegsher-ds-2025/mcp-il-professionals"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/mcp_il_professionals"]
|
|
27
|
+
|
|
28
|
+
[tool.ruff]
|
|
29
|
+
line-length = 140
|
|
30
|
+
target-version = "py311"
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint]
|
|
33
|
+
select = ["E", "F", "I", "B", "UP"]
|
|
34
|
+
|
|
35
|
+
[tool.ruff.lint.per-file-ignores]
|
|
36
|
+
"tests/*" = ["E501"]
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
testpaths = ["tests"]
|
|
40
|
+
pythonpath = ["src"]
|
|
41
|
+
markers = ["live: hits data.gov.il (run with -m live)"]
|
|
42
|
+
addopts = "-m 'not live'"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.olegsher-ds-2025/mcp-il-professionals",
|
|
4
|
+
"title": "Israel Licensed Professionals",
|
|
5
|
+
"description": "Verify licensed professionals in Israel for AI agents: real-estate brokers, registered contractors, ",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/olegsher-ds-2025/mcp-il-professionals",
|
|
8
|
+
"source": "github"
|
|
9
|
+
},
|
|
10
|
+
"version": "0.1.0",
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "pypi",
|
|
14
|
+
"identifier": "mcp-il-professionals",
|
|
15
|
+
"version": "0.1.0",
|
|
16
|
+
"transport": {
|
|
17
|
+
"type": "stdio"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Minimal async client for the data.gov.il CKAN datastore API."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
BASE_URL = "https://data.gov.il/api/3/action"
|
|
12
|
+
USER_AGENT = "mcp-il/0.1 (+https://github.com/olegsher-ds-2025)"
|
|
13
|
+
TIMEOUT = httpx.Timeout(30.0)
|
|
14
|
+
|
|
15
|
+
_DROP = {"_id", "rank", "_full_text"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CkanError(RuntimeError):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def clean_value(v: Any) -> Any:
|
|
23
|
+
"""data.gov.il registries encode a double quote as '~' (e.g. בע~מ -> בע"מ)."""
|
|
24
|
+
if isinstance(v, str):
|
|
25
|
+
v = v.replace("~", '"').strip()
|
|
26
|
+
return v or None
|
|
27
|
+
return v
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def clean_record(rec: dict) -> dict:
|
|
31
|
+
return {k: clean_value(v) for k, v in rec.items() if k not in _DROP}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def datastore_search(
|
|
35
|
+
resource_id: str,
|
|
36
|
+
*,
|
|
37
|
+
filters: dict | None = None,
|
|
38
|
+
q: str | dict | None = None,
|
|
39
|
+
limit: int = 10,
|
|
40
|
+
offset: int = 0,
|
|
41
|
+
fields: list[str] | None = None,
|
|
42
|
+
sort: str | None = None,
|
|
43
|
+
retries: int = 3,
|
|
44
|
+
) -> dict:
|
|
45
|
+
"""Query one datastore resource. Returns {'total': int, 'records': [cleaned dicts]}."""
|
|
46
|
+
params: dict[str, Any] = {"resource_id": resource_id, "limit": max(1, min(limit, 1000)), "offset": offset}
|
|
47
|
+
if filters:
|
|
48
|
+
params["filters"] = json.dumps(filters, ensure_ascii=False)
|
|
49
|
+
if q:
|
|
50
|
+
params["q"] = json.dumps(q, ensure_ascii=False) if isinstance(q, dict) else q
|
|
51
|
+
if fields:
|
|
52
|
+
params["fields"] = ",".join(fields)
|
|
53
|
+
if sort:
|
|
54
|
+
params["sort"] = sort
|
|
55
|
+
|
|
56
|
+
last_exc: Exception | None = None
|
|
57
|
+
async with httpx.AsyncClient(timeout=TIMEOUT, headers={"User-Agent": USER_AGENT}) as client:
|
|
58
|
+
for attempt in range(retries):
|
|
59
|
+
try:
|
|
60
|
+
r = await client.get(f"{BASE_URL}/datastore_search", params=params)
|
|
61
|
+
r.raise_for_status()
|
|
62
|
+
body = r.json()
|
|
63
|
+
if not body.get("success"):
|
|
64
|
+
raise CkanError(str(body.get("error")))
|
|
65
|
+
res = body["result"]
|
|
66
|
+
return {"total": res.get("total", 0), "records": [clean_record(x) for x in res.get("records", [])]}
|
|
67
|
+
except (httpx.HTTPError, CkanError, ValueError) as exc:
|
|
68
|
+
last_exc = exc
|
|
69
|
+
await asyncio.sleep(1.5 * (attempt + 1))
|
|
70
|
+
raise CkanError(f"data.gov.il query failed for {resource_id}: {last_exc}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def meta(dataset: str | list[str]) -> dict:
|
|
74
|
+
"""Standard provenance block added to every tool response."""
|
|
75
|
+
names = [dataset] if isinstance(dataset, str) else dataset
|
|
76
|
+
return {
|
|
77
|
+
"source": "data.gov.il — " + "; ".join(names),
|
|
78
|
+
"retrieved_at": datetime.now(UTC).isoformat(timespec="seconds"),
|
|
79
|
+
"attribution": "Israeli government open data (data.gov.il). Not an official service; verify critical facts "
|
|
80
|
+
"with the publishing authority.",
|
|
81
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""mcp-il-professionals — verify licensed professionals in Israel: real-estate brokers, registered
|
|
2
|
+
contractors, real-estate appraisers, CPAs, vehicle appraisers, licensed surveyors and private investigators.
|
|
3
|
+
Live data from data.gov.il (Ministries of Justice, Construction & Housing, Transport, Survey of Israel, CPA Council)."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
from fastmcp import FastMCP
|
|
10
|
+
|
|
11
|
+
from .ckan import datastore_search, meta
|
|
12
|
+
|
|
13
|
+
R_BROKERS = "a0f56034-88db-4132-8803-854bcdb01ca1" # מאגר מתווכים פעילים
|
|
14
|
+
R_CONTRACTORS = "4eb61bd6-18cf-4e7c-9f9c-e166dfa0a2d8" # פנקס הקבלנים הרשומים
|
|
15
|
+
R_APPRAISERS = "8540534a-eccd-4568-a677-652d589ed172" # מאגר שמאי מקרקעין
|
|
16
|
+
R_CPAS = "7e86def9-0899-45f4-8942-d6fe38482b1a" # מאגר רואי החשבון
|
|
17
|
+
R_VEHICLE_APPRAISERS = "4a434d65-3ca2-45e5-8026-5d9819c3f95c" # שמאי רכב
|
|
18
|
+
R_SURVEYORS = "096e5fba-420c-4822-8172-78be54767cef" # רשימת המודדים (מפ"י)
|
|
19
|
+
R_INVESTIGATORS = "a9ddb445-825f-4635-a573-fe82d2a95bf0" # חוקרים פרטיים
|
|
20
|
+
R_INVESTIGATION_FIRMS = "94722051-b0c7-4f73-b543-c558c6c2c660" # תאגידי חוקרים פרטיים
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _broker(r):
|
|
24
|
+
return {"name": r.get("שם המתווך"), "license_number": r.get("מס רשיון"), "city": r.get("עיר מגורים"),
|
|
25
|
+
"status": "active (listed in the active brokers registry)"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _appraiser(r):
|
|
29
|
+
return {"name": r.get("שם שמאי"), "license_number": r.get("מספר רשיון"), "file_number": r.get("מספר תיק"),
|
|
30
|
+
"city": r.get("עיר")}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _cpa(r):
|
|
34
|
+
return {"first_name": r.get("שם פרטי"), "last_name": r.get("שם משפחה"), "license_number": r.get("מספר רישיון"),
|
|
35
|
+
"licensed_since": r.get("תאריך קבלת רישיון"), "city": r.get("ישוב"), "status": r.get("סטטוס")}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _vehicle_appraiser(r):
|
|
39
|
+
return {"first_name": r.get("shem_prati"), "last_name": r.get("shem_mishpaha"),
|
|
40
|
+
"license_number": r.get("mispar_rishayon"), "city": r.get("yishuv")}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _surveyor(r):
|
|
44
|
+
return {"first_name": r.get("name1"), "last_name": r.get("family_name"), "license_number": r.get("number"),
|
|
45
|
+
"status": r.get("status"), "notes": r.get("information"), "certificate": r.get("cer_link")}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _investigator(r):
|
|
49
|
+
return {"first_name": r.get("שם פרטי"), "last_name": r.get("שם משפחה"), "license_number": r.get("מספר רישיון")}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _investigation_firm(r):
|
|
53
|
+
return {"firm": r.get("שם תאגיד"), "license_number": r.get("מספר רישיון")}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _contractor(r):
|
|
57
|
+
return {
|
|
58
|
+
"name": r.get("SHEM_YESHUT"), "contractor_number": r.get("MISPAR_KABLAN"),
|
|
59
|
+
"company_or_id_number": r.get("MISPAR_YESHUT"), "branch": " ".join((r.get("TEUR_ANAF") or "").split()) or None,
|
|
60
|
+
"branch_code": r.get("KOD_ANAF"), "group": r.get("KVUTZA"), "classification": r.get("SIVUG"),
|
|
61
|
+
"classification_since": r.get("TARICH_SUG"), "registered_since": (r.get("TAARICH_KABLAN") or "")[:10] or None,
|
|
62
|
+
"recognized_for_government_work": r.get("KABLAN_MUKAR"), "city": r.get("SHEM_YISHUV"),
|
|
63
|
+
"address": " ".join(x for x in (r.get("SHEM_REHOV"), r.get("MISPAR_BAIT")) if x) or None,
|
|
64
|
+
"phone": r.get("MISPAR_TEL"), "email": r.get("EMAIL"), "qualified_manager": r.get("OVDIM"),
|
|
65
|
+
"notes": r.get("HEARA"),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# profession -> (resource, mapper, license field, dataset label)
|
|
70
|
+
REGISTRIES = {
|
|
71
|
+
"real_estate_broker": (R_BROKERS, _broker, "מס רשיון", "Ministry of Justice — licensed real-estate brokers"),
|
|
72
|
+
"real_estate_appraiser": (R_APPRAISERS, _appraiser, "מספר רשיון", "Ministry of Justice — real-estate appraisers"),
|
|
73
|
+
"cpa": (R_CPAS, _cpa, "מספר רישיון", "CPA Council — certified public accountants"),
|
|
74
|
+
"vehicle_appraiser": (R_VEHICLE_APPRAISERS, _vehicle_appraiser, "mispar_rishayon",
|
|
75
|
+
"Ministry of Transport — vehicle appraisers"),
|
|
76
|
+
"surveyor": (R_SURVEYORS, _surveyor, "number", "Survey of Israel — licensed surveyors"),
|
|
77
|
+
"private_investigator": (R_INVESTIGATORS, _investigator, "מספר רישיון",
|
|
78
|
+
"Ministry of Justice — private investigators"),
|
|
79
|
+
"investigation_firm": (R_INVESTIGATION_FIRMS, _investigation_firm, "מספר רישיון",
|
|
80
|
+
"Ministry of Justice — private investigation firms"),
|
|
81
|
+
"contractor": (R_CONTRACTORS, _contractor, "MISPAR_KABLAN", "Registrar of Contractors — registered contractors"),
|
|
82
|
+
}
|
|
83
|
+
STRING_LICENSE = {"contractor"} # stored as text in the dataset
|
|
84
|
+
|
|
85
|
+
NOT_OPEN = {
|
|
86
|
+
"electrician": {
|
|
87
|
+
"regulator": "Ministry of Labor — Electricians Licensing Unit (המחלקה לרישוי חשמלאים)",
|
|
88
|
+
"how_to_verify": "Ask for the licence number and ID; check it in the Ministry of Labor online electrician "
|
|
89
|
+
"licence search. Licence grade matters (e.g. מוסמך / ראשי / בודק).",
|
|
90
|
+
"info_page": "https://www.gov.il/he/departments/units/electricity-licensing-unit",
|
|
91
|
+
},
|
|
92
|
+
"engineer_or_architect": {
|
|
93
|
+
"regulator": "Ministry of Labor — Registrar of Engineers and Architects (פנקס המהנדסים והאדריכלים)",
|
|
94
|
+
"how_to_verify": "Use the Registrar's online search on gov.il; registration and licence are separate steps.",
|
|
95
|
+
},
|
|
96
|
+
"lawyer": {
|
|
97
|
+
"regulator": "Israel Bar Association",
|
|
98
|
+
"how_to_verify": "Use the Bar Association's public lawyer search.",
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
mcp = FastMCP(
|
|
103
|
+
"mcp-il-professionals",
|
|
104
|
+
instructions=(
|
|
105
|
+
"Verify Israeli licensed professionals before hiring. Professions: " + ", ".join(REGISTRIES)
|
|
106
|
+
+ ". Use find_contractors for registered building contractors by city/branch. Electricians, engineers and "
|
|
107
|
+
"lawyers are not in open data — call list_professions for how to verify them. Names are Hebrew."
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@mcp.tool
|
|
113
|
+
async def list_professions() -> dict:
|
|
114
|
+
"""Which professions can be verified here, and how to verify those that are not open data
|
|
115
|
+
(electricians, engineers/architects, lawyers)."""
|
|
116
|
+
return {
|
|
117
|
+
"available": {k: v[3] for k, v in REGISTRIES.items()},
|
|
118
|
+
"not_in_open_data": NOT_OPEN,
|
|
119
|
+
**meta("data.gov.il catalogue"),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@mcp.tool
|
|
124
|
+
async def verify_professional(profession: str, name: str = "", license_number: str = "", city: str = "",
|
|
125
|
+
limit: int = 20) -> dict:
|
|
126
|
+
"""Check that a person or firm holds a licence in an Israeli professional registry.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
profession: one of real_estate_broker, real_estate_appraiser, cpa, vehicle_appraiser, surveyor,
|
|
130
|
+
private_investigator, investigation_firm, contractor (or electrician / engineer_or_architect /
|
|
131
|
+
lawyer — returns how to verify, these are not open data).
|
|
132
|
+
name: full or partial name in Hebrew (e.g. "גריצר דורית").
|
|
133
|
+
license_number: licence / registration number (exact match).
|
|
134
|
+
city: optional city text to narrow name searches.
|
|
135
|
+
"""
|
|
136
|
+
if profession in NOT_OPEN:
|
|
137
|
+
return {"profession": profession, "open_data": False, **NOT_OPEN[profession]}
|
|
138
|
+
if profession not in REGISTRIES:
|
|
139
|
+
raise ValueError(f"Unknown profession. Choose from: {', '.join(list(REGISTRIES) + list(NOT_OPEN))}")
|
|
140
|
+
rid, mapper, lic_field, label = REGISTRIES[profession]
|
|
141
|
+
if license_number:
|
|
142
|
+
digits = "".join(ch for ch in license_number if ch.isdigit())
|
|
143
|
+
value = digits if profession in STRING_LICENSE else int(digits)
|
|
144
|
+
res = await datastore_search(rid, filters={lic_field: value}, limit=20)
|
|
145
|
+
elif name:
|
|
146
|
+
q = " ".join(x for x in (name, city) if x)
|
|
147
|
+
res = await datastore_search(rid, q=q, limit=max(1, min(limit, 100)))
|
|
148
|
+
else:
|
|
149
|
+
raise ValueError("Provide name or license_number")
|
|
150
|
+
rows = [mapper(r) for r in res["records"]]
|
|
151
|
+
return {
|
|
152
|
+
"profession": profession,
|
|
153
|
+
"found": bool(rows),
|
|
154
|
+
"total": res["total"],
|
|
155
|
+
"results": rows,
|
|
156
|
+
"note": None if rows else "Not found. Check spelling (Hebrew), try the licence number, or the person may be "
|
|
157
|
+
"unlicensed / inactive.",
|
|
158
|
+
**meta(label),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@mcp.tool
|
|
163
|
+
async def find_contractors(city: str = "", branch: str = "", name: str = "", classification: str = "",
|
|
164
|
+
group: str = "", government_recognized_only: bool = False, limit: int = 20) -> dict:
|
|
165
|
+
"""Registered building contractors (פנקס הקבלנים) by city, branch keyword, name and classification.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
city: city in Hebrew (e.g. "מודיעין", "בת ים").
|
|
169
|
+
branch: work type keyword, e.g. "שיפוצים", "בניה", "חשמלאות", "אינסטלציה", "מיזוג", "איטום", "כבישים".
|
|
170
|
+
classification: financial-scope class "1"-"5" ("ק" = small). Higher allows larger projects.
|
|
171
|
+
group: registry group letter "א", "ב", "ג"...
|
|
172
|
+
government_recognized_only: only contractors recognized for government tenders (קבלן מוכר).
|
|
173
|
+
"""
|
|
174
|
+
filters: dict = {}
|
|
175
|
+
if classification:
|
|
176
|
+
filters["SIVUG"] = classification
|
|
177
|
+
if group:
|
|
178
|
+
filters["KVUTZA"] = group
|
|
179
|
+
q = " ".join(x for x in (name, city, branch) if x) or None
|
|
180
|
+
res = await datastore_search(R_CONTRACTORS, q=q, filters=filters or None, limit=max(1, min(limit, 200)))
|
|
181
|
+
rows = [_contractor(r) for r in res["records"]]
|
|
182
|
+
if government_recognized_only:
|
|
183
|
+
rows = [r for r in rows if (r.get("recognized_for_government_work") or "").startswith("מוכר")]
|
|
184
|
+
return {
|
|
185
|
+
"total_matches": res["total"],
|
|
186
|
+
"results": rows,
|
|
187
|
+
"legend": {"classification": "1 = smallest scope … 5 = unlimited; ק = small works",
|
|
188
|
+
"group": "Registry group letter within the branch"},
|
|
189
|
+
**meta(REGISTRIES["contractor"][3]),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@mcp.tool
|
|
194
|
+
async def contractor_profile(contractor_number: str = "", company_number: str = "") -> dict:
|
|
195
|
+
"""All registered branches and classifications for one contractor (by contractor number or company/ID number)."""
|
|
196
|
+
if contractor_number:
|
|
197
|
+
filters = {"MISPAR_KABLAN": "".join(c for c in contractor_number if c.isdigit())}
|
|
198
|
+
elif company_number:
|
|
199
|
+
filters = {"MISPAR_YESHUT": "".join(c for c in company_number if c.isdigit())}
|
|
200
|
+
else:
|
|
201
|
+
raise ValueError("Provide contractor_number or company_number")
|
|
202
|
+
res = await datastore_search(R_CONTRACTORS, filters=filters, limit=100)
|
|
203
|
+
rows = [_contractor(r) for r in res["records"]]
|
|
204
|
+
if not rows:
|
|
205
|
+
return {"found": False, **meta(REGISTRIES["contractor"][3])}
|
|
206
|
+
head = {k: rows[0][k] for k in ("name", "contractor_number", "company_or_id_number", "city", "address", "phone",
|
|
207
|
+
"email", "registered_since")}
|
|
208
|
+
branches = [{k: r[k] for k in ("branch", "group", "classification", "classification_since",
|
|
209
|
+
"recognized_for_government_work", "qualified_manager")} for r in rows]
|
|
210
|
+
return {"found": True, "contractor": head, "branches": branches, **meta(REGISTRIES["contractor"][3])}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@mcp.tool
|
|
214
|
+
async def search_all_registries(name: str, limit_per_registry: int = 5) -> dict:
|
|
215
|
+
"""Search a name across every professional registry at once — useful to see all licences a person holds."""
|
|
216
|
+
lim = max(1, min(limit_per_registry, 20))
|
|
217
|
+
keys = list(REGISTRIES)
|
|
218
|
+
res = await asyncio.gather(*(datastore_search(REGISTRIES[k][0], q=name, limit=lim) for k in keys),
|
|
219
|
+
return_exceptions=True)
|
|
220
|
+
out, errors = {}, {}
|
|
221
|
+
for k, r in zip(keys, res, strict=True):
|
|
222
|
+
if isinstance(r, Exception):
|
|
223
|
+
errors[k] = str(r)
|
|
224
|
+
elif r["records"]:
|
|
225
|
+
out[k] = {"total": r["total"], "results": [REGISTRIES[k][1](x) for x in r["records"]]}
|
|
226
|
+
body = {"query": name, "matches": out, **meta([v[3] for v in REGISTRIES.values()])}
|
|
227
|
+
if errors:
|
|
228
|
+
body["errors"] = errors
|
|
229
|
+
return body
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@mcp.tool
|
|
233
|
+
async def health() -> dict:
|
|
234
|
+
"""Check that the server can reach data.gov.il."""
|
|
235
|
+
try:
|
|
236
|
+
res = await datastore_search(R_CONTRACTORS, limit=1)
|
|
237
|
+
return {"status": "ok", "registered_contractor_rows": res["total"], **meta(REGISTRIES["contractor"][3])}
|
|
238
|
+
except Exception as exc: # noqa: BLE001
|
|
239
|
+
return {"status": "error", "error": str(exc)}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def main() -> None:
|
|
243
|
+
if os.environ.get("MCP_TRANSPORT", "stdio") == "stdio":
|
|
244
|
+
mcp.run()
|
|
245
|
+
else:
|
|
246
|
+
mcp.run(transport="http", host="0.0.0.0", port=int(os.environ.get("PORT", "8000")))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
if __name__ == "__main__":
|
|
250
|
+
main()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from fastmcp import Client
|
|
5
|
+
|
|
6
|
+
from mcp_il_professionals import server
|
|
7
|
+
|
|
8
|
+
FAKE = {"total": 1, "records": [{"dummy": "value"}]}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def _fake_search(resource_id, **kwargs):
|
|
12
|
+
return FAKE
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _call(tool, args):
|
|
16
|
+
async def run():
|
|
17
|
+
async with Client(server.mcp) as client:
|
|
18
|
+
return await client.call_tool(tool, args)
|
|
19
|
+
|
|
20
|
+
return asyncio.run(run())
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_tools_registered():
|
|
24
|
+
async def run():
|
|
25
|
+
async with Client(server.mcp) as client:
|
|
26
|
+
return {t.name for t in await client.list_tools()}
|
|
27
|
+
|
|
28
|
+
names = asyncio.run(run())
|
|
29
|
+
assert {'verify_professional', 'contractor_profile', 'find_contractors', 'search_all_registries', 'health', 'list_professions'} <= names
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_offline_call_has_provenance(monkeypatch):
|
|
33
|
+
monkeypatch.setattr(server, "datastore_search", _fake_search)
|
|
34
|
+
result = _call('verify_professional', {'profession': 'cpa', 'name': 'כהן'})
|
|
35
|
+
data = result.data if hasattr(result, "data") else result
|
|
36
|
+
assert "source" in data and "data.gov.il" in data["source"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_clean_value():
|
|
40
|
+
from mcp_il_professionals.ckan import clean_value
|
|
41
|
+
|
|
42
|
+
assert clean_value("בע~מ") == 'בע"מ'
|
|
43
|
+
assert clean_value(" ") is None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.live
|
|
47
|
+
def test_live_smoke():
|
|
48
|
+
result = _call('find_contractors', {'city': 'מודיעין', 'branch': 'שיפוצים', 'limit': 2})
|
|
49
|
+
data = result.data if hasattr(result, "data") else result
|
|
50
|
+
assert "retrieved_at" in data
|