sifa-mcp 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.
- sifa_mcp-0.1.0/LICENSE +3 -0
- sifa_mcp-0.1.0/PKG-INFO +104 -0
- sifa_mcp-0.1.0/README.md +82 -0
- sifa_mcp-0.1.0/pyproject.toml +33 -0
- sifa_mcp-0.1.0/setup.cfg +4 -0
- sifa_mcp-0.1.0/src/sifa_mcp/__init__.py +2 -0
- sifa_mcp-0.1.0/src/sifa_mcp/main.py +423 -0
- sifa_mcp-0.1.0/src/sifa_mcp.egg-info/PKG-INFO +104 -0
- sifa_mcp-0.1.0/src/sifa_mcp.egg-info/SOURCES.txt +11 -0
- sifa_mcp-0.1.0/src/sifa_mcp.egg-info/dependency_links.txt +1 -0
- sifa_mcp-0.1.0/src/sifa_mcp.egg-info/entry_points.txt +2 -0
- sifa_mcp-0.1.0/src/sifa_mcp.egg-info/requires.txt +1 -0
- sifa_mcp-0.1.0/src/sifa_mcp.egg-info/top_level.txt +1 -0
sifa_mcp-0.1.0/LICENSE
ADDED
sifa_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sifa-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for portable reputation and skills passports in East Africa
|
|
5
|
+
Author-email: Gabriel Mahia <contact@aikungfu.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/gabrielmahia/sifa-mcp
|
|
8
|
+
Project-URL: Repository, https://github.com/gabrielmahia/sifa-mcp
|
|
9
|
+
Project-URL: Issues, https://github.com/gabrielmahia/sifa-mcp/issues
|
|
10
|
+
Keywords: mcp,reputation,kenya,africa,skills,passport,workers,sifa
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Classifier: Topic :: Office/Business
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: fastmcp>=2.0.0
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# ⭐ sifa-mcp — Portable Reputation & Skills Passport MCP Server
|
|
24
|
+
|
|
25
|
+
*Sifa* = reputation/praise in Swahili.
|
|
26
|
+
|
|
27
|
+
**First portable reputation MCP server for East Africa.**
|
|
28
|
+
|
|
29
|
+
## The Structural Problem
|
|
30
|
+
|
|
31
|
+
In mature economies, reputation travels: Uber driver ratings, Yelp reviews, LinkedIn endorsements, contractor licence registries. A skilled plumber in Chicago can prove her track record to any homeowner in Los Angeles.
|
|
32
|
+
|
|
33
|
+
In Kenya, trust is tribal and local. A qualified mason in Mombasa cannot take her reputation to Nairobi. Economic mobility is throttled by the non-portability of professional identity.
|
|
34
|
+
|
|
35
|
+
**Reputation portability is a coordination technology.** Without it, hiring defaults to personal networks, which excludes the most talented people outside existing networks.
|
|
36
|
+
|
|
37
|
+
## Tools
|
|
38
|
+
|
|
39
|
+
| Tool | What it does |
|
|
40
|
+
|------|-------------|
|
|
41
|
+
| `create_worker_profile` | Create a portable reputation profile with skills, location, experience |
|
|
42
|
+
| `add_service_record` | Log a completed job with client details and rating |
|
|
43
|
+
| `get_reputation_profile` | Retrieve full profile with trust score for any requester type |
|
|
44
|
+
| `generate_reference` | Generate a formatted professional reference letter |
|
|
45
|
+
| `list_skill_categories` | Browse all NITA-aligned skill categories |
|
|
46
|
+
| `compare_workers` | Rank multiple workers by trust score and skill match |
|
|
47
|
+
|
|
48
|
+
## Trust Score
|
|
49
|
+
|
|
50
|
+
Scored 0–100 across four signals:
|
|
51
|
+
- **Job volume** (up to 30 pts) — number of completed jobs
|
|
52
|
+
- **Average quality** (up to 35 pts) — client star ratings
|
|
53
|
+
- **Verified records** (up to 20 pts) — client-OTP-confirmed completions
|
|
54
|
+
- **Tenure** (up to 15 pts) — years of documented history
|
|
55
|
+
|
|
56
|
+
Tiers: `NEW` → `BUILDING` → `ESTABLISHED` → `TRUSTED`
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install sifa-mcp # coming soon to PyPI
|
|
62
|
+
sifa-mcp # starts on stdio
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Example
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# Create a mason's portable profile
|
|
69
|
+
create_worker_profile(
|
|
70
|
+
full_name="James Mwangi",
|
|
71
|
+
phone="0712345678",
|
|
72
|
+
primary_skill="construction",
|
|
73
|
+
sub_skills="masonry,tiling,plastering",
|
|
74
|
+
location="Nairobi",
|
|
75
|
+
years_experience=8
|
|
76
|
+
)
|
|
77
|
+
# → SFA-A3F2B19C4E1D
|
|
78
|
+
|
|
79
|
+
# Log a completed job
|
|
80
|
+
add_service_record(
|
|
81
|
+
profile_id="SFA-A3F2B19C4E1D",
|
|
82
|
+
client_name="ABC Contractors",
|
|
83
|
+
client_phone="0722000000",
|
|
84
|
+
service_type="Kitchen tiling",
|
|
85
|
+
duration_days=5,
|
|
86
|
+
payment_kes=15000,
|
|
87
|
+
client_rating=5
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Generate a reference for a new job application
|
|
91
|
+
generate_reference(profile_id="SFA-A3F2B19C4E1D", reference_purpose="job_application")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Research Basis
|
|
95
|
+
|
|
96
|
+
- IFC "The Value of Reputation" (2020)
|
|
97
|
+
- ILO Skills Recognition Framework (2023)
|
|
98
|
+
- World Bank "Mobile-Based Reputation Systems" (2021)
|
|
99
|
+
- Kenya NITA (National Industrial Training Authority) trade classifications
|
|
100
|
+
|
|
101
|
+
⚠️ DEMO — Production requires Huduma Number / NIIMS integration for identity verification.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
*© 2026 Gabriel Mahia / AI Kung Fu LLC · MIT License*
|
sifa_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# ⭐ sifa-mcp — Portable Reputation & Skills Passport MCP Server
|
|
2
|
+
|
|
3
|
+
*Sifa* = reputation/praise in Swahili.
|
|
4
|
+
|
|
5
|
+
**First portable reputation MCP server for East Africa.**
|
|
6
|
+
|
|
7
|
+
## The Structural Problem
|
|
8
|
+
|
|
9
|
+
In mature economies, reputation travels: Uber driver ratings, Yelp reviews, LinkedIn endorsements, contractor licence registries. A skilled plumber in Chicago can prove her track record to any homeowner in Los Angeles.
|
|
10
|
+
|
|
11
|
+
In Kenya, trust is tribal and local. A qualified mason in Mombasa cannot take her reputation to Nairobi. Economic mobility is throttled by the non-portability of professional identity.
|
|
12
|
+
|
|
13
|
+
**Reputation portability is a coordination technology.** Without it, hiring defaults to personal networks, which excludes the most talented people outside existing networks.
|
|
14
|
+
|
|
15
|
+
## Tools
|
|
16
|
+
|
|
17
|
+
| Tool | What it does |
|
|
18
|
+
|------|-------------|
|
|
19
|
+
| `create_worker_profile` | Create a portable reputation profile with skills, location, experience |
|
|
20
|
+
| `add_service_record` | Log a completed job with client details and rating |
|
|
21
|
+
| `get_reputation_profile` | Retrieve full profile with trust score for any requester type |
|
|
22
|
+
| `generate_reference` | Generate a formatted professional reference letter |
|
|
23
|
+
| `list_skill_categories` | Browse all NITA-aligned skill categories |
|
|
24
|
+
| `compare_workers` | Rank multiple workers by trust score and skill match |
|
|
25
|
+
|
|
26
|
+
## Trust Score
|
|
27
|
+
|
|
28
|
+
Scored 0–100 across four signals:
|
|
29
|
+
- **Job volume** (up to 30 pts) — number of completed jobs
|
|
30
|
+
- **Average quality** (up to 35 pts) — client star ratings
|
|
31
|
+
- **Verified records** (up to 20 pts) — client-OTP-confirmed completions
|
|
32
|
+
- **Tenure** (up to 15 pts) — years of documented history
|
|
33
|
+
|
|
34
|
+
Tiers: `NEW` → `BUILDING` → `ESTABLISHED` → `TRUSTED`
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install sifa-mcp # coming soon to PyPI
|
|
40
|
+
sifa-mcp # starts on stdio
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Example
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
# Create a mason's portable profile
|
|
47
|
+
create_worker_profile(
|
|
48
|
+
full_name="James Mwangi",
|
|
49
|
+
phone="0712345678",
|
|
50
|
+
primary_skill="construction",
|
|
51
|
+
sub_skills="masonry,tiling,plastering",
|
|
52
|
+
location="Nairobi",
|
|
53
|
+
years_experience=8
|
|
54
|
+
)
|
|
55
|
+
# → SFA-A3F2B19C4E1D
|
|
56
|
+
|
|
57
|
+
# Log a completed job
|
|
58
|
+
add_service_record(
|
|
59
|
+
profile_id="SFA-A3F2B19C4E1D",
|
|
60
|
+
client_name="ABC Contractors",
|
|
61
|
+
client_phone="0722000000",
|
|
62
|
+
service_type="Kitchen tiling",
|
|
63
|
+
duration_days=5,
|
|
64
|
+
payment_kes=15000,
|
|
65
|
+
client_rating=5
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Generate a reference for a new job application
|
|
69
|
+
generate_reference(profile_id="SFA-A3F2B19C4E1D", reference_purpose="job_application")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Research Basis
|
|
73
|
+
|
|
74
|
+
- IFC "The Value of Reputation" (2020)
|
|
75
|
+
- ILO Skills Recognition Framework (2023)
|
|
76
|
+
- World Bank "Mobile-Based Reputation Systems" (2021)
|
|
77
|
+
- Kenya NITA (National Industrial Training Authority) trade classifications
|
|
78
|
+
|
|
79
|
+
⚠️ DEMO — Production requires Huduma Number / NIIMS integration for identity verification.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
*© 2026 Gabriel Mahia / AI Kung Fu LLC · MIT License*
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sifa-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server for portable reputation and skills passports in East Africa"
|
|
9
|
+
authors = [{name = "Gabriel Mahia", email = "contact@aikungfu.dev"}]
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
keywords = ["mcp", "reputation", "kenya", "africa", "skills", "passport", "workers", "sifa"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
|
+
"Topic :: Office/Business",
|
|
21
|
+
]
|
|
22
|
+
dependencies = ["fastmcp>=2.0.0"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/gabrielmahia/sifa-mcp"
|
|
26
|
+
Repository = "https://github.com/gabrielmahia/sifa-mcp"
|
|
27
|
+
Issues = "https://github.com/gabrielmahia/sifa-mcp/issues"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
sifa-mcp = "sifa_mcp.main:main"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
sifa_mcp-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# sifa-mcp — Portable Reputation & Skills Passport MCP Server
|
|
3
|
+
# © 2026 Gabriel Mahia / AI Kung Fu LLC — MIT License
|
|
4
|
+
#
|
|
5
|
+
# Sifa = reputation/praise in Swahili
|
|
6
|
+
#
|
|
7
|
+
# The structural problem: Trust in East Africa is tribal/family-based.
|
|
8
|
+
# A skilled mechanic in Mombasa cannot take her reputation to Nairobi.
|
|
9
|
+
# A qualified mason cannot prove his work history to a new contractor.
|
|
10
|
+
# Economic mobility is throttled by reputation portability.
|
|
11
|
+
#
|
|
12
|
+
# Western parallel: Yelp, Uber ratings, LinkedIn endorsements, Angie's List,
|
|
13
|
+
# Better Business Bureau, professional license registries.
|
|
14
|
+
# Research: IFC "The Value of Reputation" (2020), ILO Skills Recognition (2023)
|
|
15
|
+
# "Mobile-Based Reputation Systems" (World Bank, 2021)
|
|
16
|
+
#
|
|
17
|
+
# TRUST INTEGRITY: All profiles are DEMO/synthetic. No real identity verification.
|
|
18
|
+
# A production implementation requires integration with Huduma Number, NIIMS,
|
|
19
|
+
# or other Kenya DPI (Digital Public Infrastructure) identity systems.
|
|
20
|
+
# =============================================================================
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
import json
|
|
24
|
+
import datetime
|
|
25
|
+
import hashlib
|
|
26
|
+
from typing import Annotated
|
|
27
|
+
from fastmcp import FastMCP
|
|
28
|
+
|
|
29
|
+
mcp = FastMCP(
|
|
30
|
+
name="sifa-mcp",
|
|
31
|
+
instructions="""Portable reputation and skills passport MCP server for East Africa.
|
|
32
|
+
Provides tools to create worker profiles, record service history, generate
|
|
33
|
+
references, and calculate trust scores — enabling economic mobility for
|
|
34
|
+
informal workers.
|
|
35
|
+
|
|
36
|
+
IMPORTANT: All profiles are DEMO/synthetic. No real identity verification.
|
|
37
|
+
A production deployment requires integration with Kenya's Huduma Number
|
|
38
|
+
or NIIMS identity infrastructure.
|
|
39
|
+
""",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# In-memory profile store (demo — production uses persistent DB)
|
|
43
|
+
_PROFILES: dict[str, dict] = {}
|
|
44
|
+
|
|
45
|
+
SKILL_CATEGORIES = {
|
|
46
|
+
"construction": ["masonry", "plumbing", "electrical", "carpentry", "painting",
|
|
47
|
+
"roofing", "tiling", "welding", "concrete_work"],
|
|
48
|
+
"transport": ["boda_boda_riding", "tuk_tuk_driving", "truck_driving",
|
|
49
|
+
"taxi_driving", "logistics_coordination"],
|
|
50
|
+
"domestic": ["house_cleaning", "childcare", "cooking", "laundry",
|
|
51
|
+
"elder_care", "gardening"],
|
|
52
|
+
"tech": ["phone_repair", "computer_repair", "solar_installation",
|
|
53
|
+
"appliance_repair", "cctv_installation"],
|
|
54
|
+
"agriculture": ["crop_farming", "animal_husbandry", "irrigation",
|
|
55
|
+
"pest_control", "greenhouse_management"],
|
|
56
|
+
"food": ["catering", "baking", "butchery", "food_processing",
|
|
57
|
+
"market_vending"],
|
|
58
|
+
"health": ["community_health_worker", "pharmacy_assistant",
|
|
59
|
+
"lab_technician", "first_aid"],
|
|
60
|
+
"education": ["primary_tutoring", "secondary_tutoring", "vocational_training",
|
|
61
|
+
"adult_literacy"],
|
|
62
|
+
"business": ["bookkeeping", "inventory_management", "sales",
|
|
63
|
+
"customer_service", "procurement"],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def _profile_id(name: str, phone: str) -> str:
|
|
67
|
+
"""Generate a deterministic, non-reversible profile ID."""
|
|
68
|
+
raw = f"{name.lower().strip()}:{phone.strip()}"
|
|
69
|
+
return "SFA-" + hashlib.sha256(raw.encode()).hexdigest()[:12].upper()
|
|
70
|
+
|
|
71
|
+
def _trust_score(profile: dict) -> dict:
|
|
72
|
+
"""Calculate trust score from profile signals."""
|
|
73
|
+
jobs = profile.get("service_records", [])
|
|
74
|
+
verified = sum(1 for j in jobs if j.get("verified"))
|
|
75
|
+
avg_rating = (sum(j.get("rating", 0) for j in jobs) / len(jobs)) if jobs else 0
|
|
76
|
+
tenure_months = profile.get("tenure_months", 0)
|
|
77
|
+
|
|
78
|
+
# Score components (0-100)
|
|
79
|
+
volume_score = min(30, len(jobs) * 3) # Up to 30 pts for job count
|
|
80
|
+
quality_score = int(avg_rating / 5 * 35) # Up to 35 pts for avg rating
|
|
81
|
+
verified_score = min(20, verified * 5) # Up to 20 pts for verified jobs
|
|
82
|
+
tenure_score = min(15, tenure_months // 3) # Up to 15 pts for tenure
|
|
83
|
+
|
|
84
|
+
total = volume_score + quality_score + verified_score + tenure_score
|
|
85
|
+
|
|
86
|
+
tier = ("TRUSTED" if total >= 70 else
|
|
87
|
+
"ESTABLISHED" if total >= 45 else
|
|
88
|
+
"BUILDING" if total >= 20 else "NEW")
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"total_score": total,
|
|
92
|
+
"tier": tier,
|
|
93
|
+
"breakdown": {
|
|
94
|
+
"job_volume": volume_score,
|
|
95
|
+
"average_quality": quality_score,
|
|
96
|
+
"verified_records": verified_score,
|
|
97
|
+
"tenure": tenure_score,
|
|
98
|
+
},
|
|
99
|
+
"tier_description": {
|
|
100
|
+
"TRUSTED": "Strong, verifiable history. High confidence for new engagements.",
|
|
101
|
+
"ESTABLISHED": "Good track record. Suitable for most engagements.",
|
|
102
|
+
"BUILDING": "Growing history. Recommend starting with small jobs.",
|
|
103
|
+
"NEW": "Limited history. Ask for references and start with a trial job.",
|
|
104
|
+
}[tier],
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@mcp.tool(
|
|
109
|
+
description=(
|
|
110
|
+
"Create a portable worker reputation profile. "
|
|
111
|
+
"Western parallel: LinkedIn profile creation, Angie's List contractor profile. "
|
|
112
|
+
"This is the 'economic passport' that travels with the worker. "
|
|
113
|
+
"DEMO — no real identity verification in this prototype."
|
|
114
|
+
),
|
|
115
|
+
annotations={"readOnlyHint": False, "destructiveHint": False},
|
|
116
|
+
)
|
|
117
|
+
def create_worker_profile(
|
|
118
|
+
full_name: Annotated[str, "Worker's full name"],
|
|
119
|
+
phone: Annotated[str, "M-PESA phone number (used as identity anchor, not stored in plain text)"],
|
|
120
|
+
primary_skill: Annotated[str, "Primary skill category (see skill_categories tool)"],
|
|
121
|
+
sub_skills: Annotated[str, "Comma-separated list of specific skills (e.g., 'masonry,tiling,plastering')"],
|
|
122
|
+
location: Annotated[str, "Current base location (town or county)"],
|
|
123
|
+
years_experience: Annotated[int, "Years of experience in primary skill (0-40)"],
|
|
124
|
+
languages: Annotated[str, "Languages spoken (e.g., 'Swahili,English,Kikuyu')"] = "Swahili",
|
|
125
|
+
) -> dict:
|
|
126
|
+
profile_id = _profile_id(full_name, phone)
|
|
127
|
+
|
|
128
|
+
profile = {
|
|
129
|
+
"profile_id": profile_id,
|
|
130
|
+
"name": full_name,
|
|
131
|
+
"phone_masked": phone[-4:].rjust(len(phone), "*"),
|
|
132
|
+
"primary_skill": primary_skill.lower(),
|
|
133
|
+
"sub_skills": [s.strip() for s in sub_skills.split(",")],
|
|
134
|
+
"location": location,
|
|
135
|
+
"years_experience": years_experience,
|
|
136
|
+
"languages": [l.strip() for l in languages.split(",")],
|
|
137
|
+
"created": datetime.date.today().isoformat(),
|
|
138
|
+
"service_records": [],
|
|
139
|
+
"tenure_months": max(0, years_experience * 12),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_PROFILES[profile_id] = profile
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
"status": "OK",
|
|
146
|
+
"profile_id": profile_id,
|
|
147
|
+
"profile_summary": {
|
|
148
|
+
"name": full_name,
|
|
149
|
+
"primary_skill": primary_skill,
|
|
150
|
+
"location": location,
|
|
151
|
+
"experience_years": years_experience,
|
|
152
|
+
"trust_score": _trust_score(profile),
|
|
153
|
+
},
|
|
154
|
+
"next_steps": [
|
|
155
|
+
"Add service records with add_service_record to build trust score",
|
|
156
|
+
"Share your profile_id with clients so they can verify your history",
|
|
157
|
+
"After 5+ verified records, generate a reference with generate_reference",
|
|
158
|
+
],
|
|
159
|
+
"share_text": (
|
|
160
|
+
f"My Sifa Profile: {profile_id} · {primary_skill} · {years_experience}yr exp · {location}"
|
|
161
|
+
),
|
|
162
|
+
"note": "DEMO — No real identity verification. Production requires Huduma Number integration.",
|
|
163
|
+
"source": "sifa-mcp. Reference: IFC 'The Value of Reputation' (2020).",
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@mcp.tool(
|
|
168
|
+
description=(
|
|
169
|
+
"Add a completed service record to a worker's profile. "
|
|
170
|
+
"Western parallel: Uber trip completion, Angie's List job review, "
|
|
171
|
+
"Upwork completed contract. Each record builds the reputation score. "
|
|
172
|
+
"DEMO — client verification is simulated."
|
|
173
|
+
),
|
|
174
|
+
annotations={"readOnlyHint": False, "destructiveHint": False},
|
|
175
|
+
)
|
|
176
|
+
def add_service_record(
|
|
177
|
+
profile_id: Annotated[str, "Worker profile ID (SFA-XXXXXXXXXXXX format)"],
|
|
178
|
+
client_name: Annotated[str, "Client name (or business name)"],
|
|
179
|
+
client_phone: Annotated[str, "Client M-PESA number (for verification anchor)"],
|
|
180
|
+
service_type: Annotated[str, "Type of service performed"],
|
|
181
|
+
duration_days: Annotated[int, "Duration of the job in days"],
|
|
182
|
+
payment_kes: Annotated[int, "Amount paid in KES (0 if unpaid/pro bono)"],
|
|
183
|
+
client_rating: Annotated[int, "Client rating 1-5 stars"],
|
|
184
|
+
notes: Annotated[str, "Brief description of work done"] = "",
|
|
185
|
+
) -> dict:
|
|
186
|
+
if profile_id not in _PROFILES:
|
|
187
|
+
return {"status": "NOT_FOUND",
|
|
188
|
+
"message": f"Profile {profile_id} not found. Create it first with create_worker_profile."}
|
|
189
|
+
|
|
190
|
+
if not 1 <= client_rating <= 5:
|
|
191
|
+
return {"status": "ERROR", "message": "Rating must be 1-5"}
|
|
192
|
+
|
|
193
|
+
record = {
|
|
194
|
+
"record_id": f"REC-{datetime.date.today().isoformat()}-{len(_PROFILES[profile_id]['service_records'])+1:03d}",
|
|
195
|
+
"date": datetime.date.today().isoformat(),
|
|
196
|
+
"service": service_type,
|
|
197
|
+
"client": client_name,
|
|
198
|
+
"client_phone_masked": client_phone[-4:].rjust(len(client_phone), "*"),
|
|
199
|
+
"duration_days": duration_days,
|
|
200
|
+
"payment_kes": payment_kes,
|
|
201
|
+
"rating": client_rating,
|
|
202
|
+
"notes": notes,
|
|
203
|
+
"verified": True, # In demo, all client-submitted records are marked verified
|
|
204
|
+
# Production: requires OTP confirmation from client phone
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_PROFILES[profile_id]["service_records"].append(record)
|
|
208
|
+
new_score = _trust_score(_PROFILES[profile_id])
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
"status": "OK",
|
|
212
|
+
"record_id": record["record_id"],
|
|
213
|
+
"profile_id": profile_id,
|
|
214
|
+
"new_trust_score": new_score,
|
|
215
|
+
"total_records": len(_PROFILES[profile_id]["service_records"]),
|
|
216
|
+
"verification_note": (
|
|
217
|
+
"DEMO: Record marked verified. In production, client receives an OTP "
|
|
218
|
+
"to confirm the job completion — prevents workers from fabricating history."
|
|
219
|
+
),
|
|
220
|
+
"source": "sifa-mcp.",
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@mcp.tool(
|
|
225
|
+
description=(
|
|
226
|
+
"Retrieve a worker's full reputation profile including trust score. "
|
|
227
|
+
"Western parallel: Checking a contractor's BBB rating or Upwork JSS. "
|
|
228
|
+
"DEMO data."
|
|
229
|
+
),
|
|
230
|
+
annotations={"readOnlyHint": True},
|
|
231
|
+
)
|
|
232
|
+
def get_reputation_profile(
|
|
233
|
+
profile_id: Annotated[str, "Worker profile ID to retrieve"],
|
|
234
|
+
requester_context: Annotated[str, "Who is requesting (employer, SACCO, bank, NGO)"] = "employer",
|
|
235
|
+
) -> dict:
|
|
236
|
+
if profile_id not in _PROFILES:
|
|
237
|
+
return {"status": "NOT_FOUND",
|
|
238
|
+
"message": "Profile not found. Worker may not have registered yet."}
|
|
239
|
+
|
|
240
|
+
profile = _PROFILES[profile_id]
|
|
241
|
+
score = _trust_score(profile)
|
|
242
|
+
records = profile.get("service_records", [])
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
"status": "OK",
|
|
246
|
+
"profile": {
|
|
247
|
+
"profile_id": profile_id,
|
|
248
|
+
"name": profile["name"],
|
|
249
|
+
"primary_skill": profile["primary_skill"],
|
|
250
|
+
"skills": profile["sub_skills"],
|
|
251
|
+
"location": profile["location"],
|
|
252
|
+
"experience_years": profile["years_experience"],
|
|
253
|
+
"languages": profile["languages"],
|
|
254
|
+
"member_since": profile["created"],
|
|
255
|
+
},
|
|
256
|
+
"trust_score": score,
|
|
257
|
+
"service_history": {
|
|
258
|
+
"total_jobs": len(records),
|
|
259
|
+
"verified_jobs": sum(1 for r in records if r.get("verified")),
|
|
260
|
+
"average_rating": round(sum(r["rating"] for r in records) / len(records), 1) if records else 0,
|
|
261
|
+
"total_earned_kes": sum(r.get("payment_kes", 0) for r in records),
|
|
262
|
+
"recent_jobs": records[-3:] if records else [],
|
|
263
|
+
},
|
|
264
|
+
"recommendation_for_requester": {
|
|
265
|
+
"employer": f"{'Recommended' if score['tier'] in ('TRUSTED','ESTABLISHED') else 'Start with a trial job'}",
|
|
266
|
+
"SACCO": f"{'Eligible for member loan' if score['tier'] == 'TRUSTED' else 'Insufficient history for loan'}",
|
|
267
|
+
"bank": f"{'Consider for microfinance product' if score['total_score'] >= 60 else 'Insufficient reputation history'}",
|
|
268
|
+
"NGO": f"{'Strong candidate for programme participation' if score['total_score'] >= 30 else 'Emerging candidate'}",
|
|
269
|
+
}.get(requester_context, "Review profile and trust score manually."),
|
|
270
|
+
"note": "DEMO — Synthetic profile. Production requires Huduma Number verification.",
|
|
271
|
+
"source": "sifa-mcp.",
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@mcp.tool(
|
|
276
|
+
description=(
|
|
277
|
+
"Generate a structured professional reference letter for a worker. "
|
|
278
|
+
"Western parallel: LinkedIn recommendation, employer reference letter. "
|
|
279
|
+
"Outputs a formatted reference that can be printed, shared as PDF, "
|
|
280
|
+
"or verified by QR code in production. DEMO."
|
|
281
|
+
),
|
|
282
|
+
annotations={"readOnlyHint": True},
|
|
283
|
+
)
|
|
284
|
+
def generate_reference(
|
|
285
|
+
profile_id: Annotated[str, "Worker profile ID"],
|
|
286
|
+
reference_purpose: Annotated[str, "Purpose: job_application, loan_application, tenancy, SACCO_membership, NGO_programme"] = "job_application",
|
|
287
|
+
) -> dict:
|
|
288
|
+
if profile_id not in _PROFILES:
|
|
289
|
+
return {"status": "NOT_FOUND", "message": "Profile not found."}
|
|
290
|
+
|
|
291
|
+
profile = _PROFILES[profile_id]
|
|
292
|
+
score = _trust_score(profile)
|
|
293
|
+
records = profile.get("service_records", [])
|
|
294
|
+
today = datetime.date.today().strftime("%B %d, %Y")
|
|
295
|
+
avg_rating = round(sum(r["rating"] for r in records) / len(records), 1) if records else 0
|
|
296
|
+
total_earned = sum(r.get("payment_kes", 0) for r in records)
|
|
297
|
+
|
|
298
|
+
reference_text = f"""SIFA REPUTATION REFERENCE
|
|
299
|
+
Issued: {today}
|
|
300
|
+
Profile ID: {profile_id}
|
|
301
|
+
Verification: sifa-mcp.africastack.io/{profile_id} [DEMO]
|
|
302
|
+
|
|
303
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
304
|
+
|
|
305
|
+
TO WHOM IT MAY CONCERN
|
|
306
|
+
|
|
307
|
+
This reference is issued on behalf of {profile["name"]}, whose portable
|
|
308
|
+
reputation profile ({profile_id}) has been verified by the Sifa network.
|
|
309
|
+
|
|
310
|
+
PROFESSIONAL SUMMARY
|
|
311
|
+
Name: {profile["name"]}
|
|
312
|
+
Primary Skill: {profile["primary_skill"].title()}
|
|
313
|
+
Skills: {", ".join(profile["sub_skills"])}
|
|
314
|
+
Experience: {profile["years_experience"]} years
|
|
315
|
+
Location: {profile["location"]}
|
|
316
|
+
Languages: {", ".join(profile["languages"])}
|
|
317
|
+
|
|
318
|
+
REPUTATION SUMMARY
|
|
319
|
+
Trust Tier: {score["tier"]}
|
|
320
|
+
Trust Score: {score["total_score"]}/100
|
|
321
|
+
Completed Jobs: {len(records)}
|
|
322
|
+
Verified Jobs: {sum(1 for r in records if r.get("verified"))}
|
|
323
|
+
Average Rating: {avg_rating}/5.0 stars
|
|
324
|
+
Total Earned: KES {total_earned:,}
|
|
325
|
+
|
|
326
|
+
ASSESSMENT
|
|
327
|
+
{profile["name"]} has demonstrated {score["tier_description"].lower()}
|
|
328
|
+
|
|
329
|
+
{"This reference is provided for: " + reference_purpose.replace("_", " ").title() + "."}
|
|
330
|
+
|
|
331
|
+
VERIFICATION
|
|
332
|
+
This profile can be independently verified at the QR code above or by
|
|
333
|
+
contacting the Sifa network at sifa-mcp.africastack.io [DEMO].
|
|
334
|
+
|
|
335
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
336
|
+
⚠️ DEMO DOCUMENT — Synthetic for educational purposes.
|
|
337
|
+
Production version requires Huduma Number integration.
|
|
338
|
+
sifa-mcp · © 2026 Gabriel Mahia / AI Kung Fu LLC · MIT License
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
"status": "OK",
|
|
343
|
+
"profile_id": profile_id,
|
|
344
|
+
"reference_purpose": reference_purpose,
|
|
345
|
+
"reference_text": reference_text,
|
|
346
|
+
"trust_score": score,
|
|
347
|
+
"note": "DEMO — Reference is illustrative only. Production version uses digital signatures and Huduma Number verification.",
|
|
348
|
+
"source": "sifa-mcp. Reference: ILO Skills Recognition Framework (2023).",
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@mcp.tool(
|
|
353
|
+
description=(
|
|
354
|
+
"List all skill categories and subcategories available in the Sifa system. "
|
|
355
|
+
"Use this to see valid skill values for create_worker_profile."
|
|
356
|
+
),
|
|
357
|
+
annotations={"readOnlyHint": True},
|
|
358
|
+
)
|
|
359
|
+
def list_skill_categories() -> dict:
|
|
360
|
+
return {
|
|
361
|
+
"status": "OK",
|
|
362
|
+
"categories": SKILL_CATEGORIES,
|
|
363
|
+
"total_skills": sum(len(v) for v in SKILL_CATEGORIES.values()),
|
|
364
|
+
"usage": "Pass the category name as primary_skill and specific skills as sub_skills in create_worker_profile",
|
|
365
|
+
"source": "sifa-mcp. Categories aligned with Kenya NITA (National Industrial Training Authority) trade classifications.",
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@mcp.tool(
|
|
370
|
+
description=(
|
|
371
|
+
"Compare the reputation scores of multiple workers for a specific job. "
|
|
372
|
+
"Western parallel: Contractor comparison on Thumbtack, Bark, or Houzz. "
|
|
373
|
+
"DEMO data."
|
|
374
|
+
),
|
|
375
|
+
annotations={"readOnlyHint": True},
|
|
376
|
+
)
|
|
377
|
+
def compare_workers(
|
|
378
|
+
profile_ids: Annotated[str, "Comma-separated list of profile IDs to compare"],
|
|
379
|
+
required_skill: Annotated[str, "Skill required for the job"] = "",
|
|
380
|
+
) -> dict:
|
|
381
|
+
ids = [p.strip() for p in profile_ids.split(",")]
|
|
382
|
+
results = []
|
|
383
|
+
for pid in ids:
|
|
384
|
+
if pid in _PROFILES:
|
|
385
|
+
p = _PROFILES[pid]
|
|
386
|
+
s = _trust_score(p)
|
|
387
|
+
results.append({
|
|
388
|
+
"profile_id": pid,
|
|
389
|
+
"name": p["name"],
|
|
390
|
+
"primary_skill": p["primary_skill"],
|
|
391
|
+
"location": p["location"],
|
|
392
|
+
"trust_score": s["total_score"],
|
|
393
|
+
"trust_tier": s["tier"],
|
|
394
|
+
"total_jobs": len(p.get("service_records", [])),
|
|
395
|
+
"experience_years": p["years_experience"],
|
|
396
|
+
"skill_match": not required_skill or required_skill.lower() in [
|
|
397
|
+
sk.lower() for sk in p.get("sub_skills", [])] or
|
|
398
|
+
required_skill.lower() == p["primary_skill"].lower(),
|
|
399
|
+
})
|
|
400
|
+
else:
|
|
401
|
+
results.append({"profile_id": pid, "status": "NOT_FOUND"})
|
|
402
|
+
|
|
403
|
+
results_sorted = sorted(
|
|
404
|
+
[r for r in results if "trust_score" in r],
|
|
405
|
+
key=lambda x: (x.get("skill_match", False), x.get("trust_score", 0)),
|
|
406
|
+
reverse=True
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
"status": "OK",
|
|
411
|
+
"required_skill": required_skill or "any",
|
|
412
|
+
"ranked_workers": results_sorted,
|
|
413
|
+
"recommendation": results_sorted[0] if results_sorted else None,
|
|
414
|
+
"note": "DEMO — Ranking based on synthetic trust scores. Production uses verified job history.",
|
|
415
|
+
"source": "sifa-mcp.",
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def main():
|
|
420
|
+
mcp.run()
|
|
421
|
+
|
|
422
|
+
if __name__ == "__main__":
|
|
423
|
+
main()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sifa-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for portable reputation and skills passports in East Africa
|
|
5
|
+
Author-email: Gabriel Mahia <contact@aikungfu.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/gabrielmahia/sifa-mcp
|
|
8
|
+
Project-URL: Repository, https://github.com/gabrielmahia/sifa-mcp
|
|
9
|
+
Project-URL: Issues, https://github.com/gabrielmahia/sifa-mcp/issues
|
|
10
|
+
Keywords: mcp,reputation,kenya,africa,skills,passport,workers,sifa
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Classifier: Topic :: Office/Business
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: fastmcp>=2.0.0
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# ⭐ sifa-mcp — Portable Reputation & Skills Passport MCP Server
|
|
24
|
+
|
|
25
|
+
*Sifa* = reputation/praise in Swahili.
|
|
26
|
+
|
|
27
|
+
**First portable reputation MCP server for East Africa.**
|
|
28
|
+
|
|
29
|
+
## The Structural Problem
|
|
30
|
+
|
|
31
|
+
In mature economies, reputation travels: Uber driver ratings, Yelp reviews, LinkedIn endorsements, contractor licence registries. A skilled plumber in Chicago can prove her track record to any homeowner in Los Angeles.
|
|
32
|
+
|
|
33
|
+
In Kenya, trust is tribal and local. A qualified mason in Mombasa cannot take her reputation to Nairobi. Economic mobility is throttled by the non-portability of professional identity.
|
|
34
|
+
|
|
35
|
+
**Reputation portability is a coordination technology.** Without it, hiring defaults to personal networks, which excludes the most talented people outside existing networks.
|
|
36
|
+
|
|
37
|
+
## Tools
|
|
38
|
+
|
|
39
|
+
| Tool | What it does |
|
|
40
|
+
|------|-------------|
|
|
41
|
+
| `create_worker_profile` | Create a portable reputation profile with skills, location, experience |
|
|
42
|
+
| `add_service_record` | Log a completed job with client details and rating |
|
|
43
|
+
| `get_reputation_profile` | Retrieve full profile with trust score for any requester type |
|
|
44
|
+
| `generate_reference` | Generate a formatted professional reference letter |
|
|
45
|
+
| `list_skill_categories` | Browse all NITA-aligned skill categories |
|
|
46
|
+
| `compare_workers` | Rank multiple workers by trust score and skill match |
|
|
47
|
+
|
|
48
|
+
## Trust Score
|
|
49
|
+
|
|
50
|
+
Scored 0–100 across four signals:
|
|
51
|
+
- **Job volume** (up to 30 pts) — number of completed jobs
|
|
52
|
+
- **Average quality** (up to 35 pts) — client star ratings
|
|
53
|
+
- **Verified records** (up to 20 pts) — client-OTP-confirmed completions
|
|
54
|
+
- **Tenure** (up to 15 pts) — years of documented history
|
|
55
|
+
|
|
56
|
+
Tiers: `NEW` → `BUILDING` → `ESTABLISHED` → `TRUSTED`
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install sifa-mcp # coming soon to PyPI
|
|
62
|
+
sifa-mcp # starts on stdio
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Example
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# Create a mason's portable profile
|
|
69
|
+
create_worker_profile(
|
|
70
|
+
full_name="James Mwangi",
|
|
71
|
+
phone="0712345678",
|
|
72
|
+
primary_skill="construction",
|
|
73
|
+
sub_skills="masonry,tiling,plastering",
|
|
74
|
+
location="Nairobi",
|
|
75
|
+
years_experience=8
|
|
76
|
+
)
|
|
77
|
+
# → SFA-A3F2B19C4E1D
|
|
78
|
+
|
|
79
|
+
# Log a completed job
|
|
80
|
+
add_service_record(
|
|
81
|
+
profile_id="SFA-A3F2B19C4E1D",
|
|
82
|
+
client_name="ABC Contractors",
|
|
83
|
+
client_phone="0722000000",
|
|
84
|
+
service_type="Kitchen tiling",
|
|
85
|
+
duration_days=5,
|
|
86
|
+
payment_kes=15000,
|
|
87
|
+
client_rating=5
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Generate a reference for a new job application
|
|
91
|
+
generate_reference(profile_id="SFA-A3F2B19C4E1D", reference_purpose="job_application")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Research Basis
|
|
95
|
+
|
|
96
|
+
- IFC "The Value of Reputation" (2020)
|
|
97
|
+
- ILO Skills Recognition Framework (2023)
|
|
98
|
+
- World Bank "Mobile-Based Reputation Systems" (2021)
|
|
99
|
+
- Kenya NITA (National Industrial Training Authority) trade classifications
|
|
100
|
+
|
|
101
|
+
⚠️ DEMO — Production requires Huduma Number / NIIMS integration for identity verification.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
*© 2026 Gabriel Mahia / AI Kung Fu LLC · MIT License*
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/sifa_mcp/__init__.py
|
|
5
|
+
src/sifa_mcp/main.py
|
|
6
|
+
src/sifa_mcp.egg-info/PKG-INFO
|
|
7
|
+
src/sifa_mcp.egg-info/SOURCES.txt
|
|
8
|
+
src/sifa_mcp.egg-info/dependency_links.txt
|
|
9
|
+
src/sifa_mcp.egg-info/entry_points.txt
|
|
10
|
+
src/sifa_mcp.egg-info/requires.txt
|
|
11
|
+
src/sifa_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fastmcp>=2.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sifa_mcp
|