level-up-mcp-server-cn 0.4.0
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.
- package/.claude/projects/c--Users-klexi-OneDrive-Desktop-Levelup-level-up-mcp-server/memory/project_testing_service.md +11 -0
- package/.claude/settings.local.json +10 -0
- package/.env.example +19 -0
- package/CLAUDE.md +222 -0
- package/CODE_REVIEW.md +282 -0
- package/LICENSE +64 -0
- package/README.md +198 -0
- package/dist/constants.d.ts +33 -0
- package/dist/constants.js +78 -0
- package/dist/constants.js.map +1 -0
- package/dist/data/quest-seeds.d.ts +18 -0
- package/dist/data/quest-seeds.js +380 -0
- package/dist/data/quest-seeds.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/common.d.ts +33 -0
- package/dist/schemas/common.js +96 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/services/dispatcher.d.ts +27 -0
- package/dist/services/dispatcher.js +47 -0
- package/dist/services/dispatcher.js.map +1 -0
- package/dist/services/errors.d.ts +56 -0
- package/dist/services/errors.js +99 -0
- package/dist/services/errors.js.map +1 -0
- package/dist/services/format.d.ts +74 -0
- package/dist/services/format.js +144 -0
- package/dist/services/format.js.map +1 -0
- package/dist/services/ownership.d.ts +19 -0
- package/dist/services/ownership.js +79 -0
- package/dist/services/ownership.js.map +1 -0
- package/dist/services/quality-gate.d.ts +45 -0
- package/dist/services/quality-gate.js +131 -0
- package/dist/services/quality-gate.js.map +1 -0
- package/dist/services/rate-limit.d.ts +12 -0
- package/dist/services/rate-limit.js +49 -0
- package/dist/services/rate-limit.js.map +1 -0
- package/dist/services/register.d.ts +49 -0
- package/dist/services/register.js +63 -0
- package/dist/services/register.js.map +1 -0
- package/dist/services/supabase.d.ts +10 -0
- package/dist/services/supabase.js +79 -0
- package/dist/services/supabase.js.map +1 -0
- package/dist/tools/achievements.d.ts +6 -0
- package/dist/tools/achievements.js +242 -0
- package/dist/tools/achievements.js.map +1 -0
- package/dist/tools/admin.d.ts +16 -0
- package/dist/tools/admin.js +328 -0
- package/dist/tools/admin.js.map +1 -0
- package/dist/tools/agents.d.ts +3 -0
- package/dist/tools/agents.js +400 -0
- package/dist/tools/agents.js.map +1 -0
- package/dist/tools/bootstrap.d.ts +17 -0
- package/dist/tools/bootstrap.js +565 -0
- package/dist/tools/bootstrap.js.map +1 -0
- package/dist/tools/dispatchers/admin.d.ts +3 -0
- package/dist/tools/dispatchers/admin.js +50 -0
- package/dist/tools/dispatchers/admin.js.map +1 -0
- package/dist/tools/dispatchers/eval.d.ts +3 -0
- package/dist/tools/dispatchers/eval.js +40 -0
- package/dist/tools/dispatchers/eval.js.map +1 -0
- package/dist/tools/dispatchers/quests.d.ts +3 -0
- package/dist/tools/dispatchers/quests.js +60 -0
- package/dist/tools/dispatchers/quests.js.map +1 -0
- package/dist/tools/dispatchers/session.d.ts +3 -0
- package/dist/tools/dispatchers/session.js +38 -0
- package/dist/tools/dispatchers/session.js.map +1 -0
- package/dist/tools/dispatchers/skills.d.ts +3 -0
- package/dist/tools/dispatchers/skills.js +49 -0
- package/dist/tools/dispatchers/skills.js.map +1 -0
- package/dist/tools/dispatchers/tasks.d.ts +3 -0
- package/dist/tools/dispatchers/tasks.js +53 -0
- package/dist/tools/dispatchers/tasks.js.map +1 -0
- package/dist/tools/dispatchers/users.d.ts +3 -0
- package/dist/tools/dispatchers/users.js +65 -0
- package/dist/tools/dispatchers/users.js.map +1 -0
- package/dist/tools/dispatchers/xp.d.ts +3 -0
- package/dist/tools/dispatchers/xp.js +51 -0
- package/dist/tools/dispatchers/xp.js.map +1 -0
- package/dist/tools/growth-plan.d.ts +5 -0
- package/dist/tools/growth-plan.js +791 -0
- package/dist/tools/growth-plan.js.map +1 -0
- package/dist/tools/leaderboards.d.ts +10 -0
- package/dist/tools/leaderboards.js +279 -0
- package/dist/tools/leaderboards.js.map +1 -0
- package/dist/tools/leveling.d.ts +24 -0
- package/dist/tools/leveling.js +356 -0
- package/dist/tools/leveling.js.map +1 -0
- package/dist/tools/metrics.d.ts +3 -0
- package/dist/tools/metrics.js +247 -0
- package/dist/tools/metrics.js.map +1 -0
- package/dist/tools/quests.d.ts +5 -0
- package/dist/tools/quests.js +586 -0
- package/dist/tools/quests.js.map +1 -0
- package/dist/tools/ratings.d.ts +11 -0
- package/dist/tools/ratings.js +564 -0
- package/dist/tools/ratings.js.map +1 -0
- package/dist/tools/skills.d.ts +66 -0
- package/dist/tools/skills.js +1112 -0
- package/dist/tools/skills.js.map +1 -0
- package/dist/tools/system.d.ts +31 -0
- package/dist/tools/system.js +605 -0
- package/dist/tools/system.js.map +1 -0
- package/dist/tools/tasks.d.ts +73 -0
- package/dist/tools/tasks.js +1572 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/users.d.ts +97 -0
- package/dist/tools/users.js +1306 -0
- package/dist/tools/users.js.map +1 -0
- package/dist/tools/xp.d.ts +38 -0
- package/dist/tools/xp.js +670 -0
- package/dist/tools/xp.js.map +1 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/docs/recommended-skillsets.md +622 -0
- package/docs/skills-and-abilities-review.md +672 -0
- package/docs/v0.3-roadmap.md +191 -0
- package/package.json +35 -0
- package/sql/agent_pending_installs.sql +28 -0
- package/sql/award_class_xp.sql +81 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/gotrue-version +1 -0
- package/supabase/.temp/pooler-url +1 -0
- package/supabase/.temp/postgres-version +1 -0
- package/supabase/.temp/project-ref +1 -0
- package/supabase/.temp/rest-version +1 -0
- package/supabase/.temp/storage-migration +1 -0
- package/supabase/.temp/storage-version +1 -0
- package/supabase/migrations/20260314000000_anon_rls_policies.sql +311 -0
- package/supabase/migrations/20260314000001_ownership_rpcs.sql +382 -0
- package/supabase/migrations/20260314000002_evidence_and_growth_plan.sql +97 -0
- package/supabase/migrations/20260317000000_seed_quests.sql +62 -0
- package/supabase/migrations/20260317000001_star_cooldown_and_fixes.sql +16 -0
- package/supabase/migrations/20260318000000_restore_rank_names.sql +25 -0
- package/supabase/migrations/20260320000000_chinese_rank_names.sql +24 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Testing service deferred
|
|
3
|
+
description: Testing service (evidence verification, skill tests) pushed to later update, not v0.3
|
|
4
|
+
type: project
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Testing service is deferred to a later version (not v0.3).
|
|
8
|
+
|
|
9
|
+
**Why:** User wants to focus on core loop stability, skill system, and feedback from v0.2.x first. Testing service adds complexity that isn't needed until the base system is proven.
|
|
10
|
+
|
|
11
|
+
**How to apply:** Don't propose or build testing service features in near-term work. Focus on making the existing task → XP → level-up flow solid.
|
package/.env.example
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# Level-Up MCP Server — Environment Variables
|
|
3
|
+
# ============================================================
|
|
4
|
+
# Copy this file to .env and fill in your values.
|
|
5
|
+
# DO NOT commit .env to version control!
|
|
6
|
+
# ============================================================
|
|
7
|
+
|
|
8
|
+
# Your Supabase project URL (from Settings → API in the Supabase dashboard)
|
|
9
|
+
SUPABASE_URL=https://your-project-id.supabase.co
|
|
10
|
+
|
|
11
|
+
# Your Supabase service_role key (from Settings → API → service_role key)
|
|
12
|
+
# ⚠️ This key has FULL access to your database. Keep it secret!
|
|
13
|
+
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
14
|
+
|
|
15
|
+
# Transport mode: "stdio" (default) or "http"
|
|
16
|
+
# TRANSPORT=stdio
|
|
17
|
+
|
|
18
|
+
# Port for HTTP transport (default: 3000)
|
|
19
|
+
# PORT=3000
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# CLAUDE.md — Level-Up MCP Server
|
|
2
|
+
|
|
3
|
+
## What Is This Project?
|
|
4
|
+
|
|
5
|
+
Level-Up is a gamification MCP server that tracks XP, levels, skills, and quests for humans and AI agents. It connects to any MCP-compatible client (Claude Desktop, OpenClaw, etc.) and awards XP for productive work.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
- **MCP Server**: TypeScript, @modelcontextprotocol/sdk v1.x
|
|
10
|
+
- **Database**: Supabase (Postgres) — 45 tables, RLS enabled
|
|
11
|
+
- **Transport**: stdio (default, for Claude Desktop/OpenClaw) + Streamable HTTP (Express, for remote)
|
|
12
|
+
- **Security**: Critical writes (XP, levels, achievements) go through Postgres SECURITY DEFINER functions via `supabase.rpc()`. Anon key users cannot directly modify XP/level tables.
|
|
13
|
+
- **Distribution**: npm package `level-up-mcp-server`, zero-config with hardcoded anon key fallback
|
|
14
|
+
|
|
15
|
+
## File Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/
|
|
19
|
+
├── index.ts # Entry point — registers all tools, starts transport
|
|
20
|
+
├── constants.ts # Shared constants
|
|
21
|
+
├── types.ts # TypeScript type definitions
|
|
22
|
+
├── schemas/
|
|
23
|
+
│ └── common.ts # Zod schemas (uuid, pagination, enums)
|
|
24
|
+
├── services/
|
|
25
|
+
│ ├── supabase.ts # Supabase client (anon key hardcoded as fallback)
|
|
26
|
+
│ ├── errors.ts # Error handling helpers (ok, fail, handleSupabaseError)
|
|
27
|
+
│ ├── format.ts # Pagination and formatting helpers
|
|
28
|
+
│ ├── rate-limit.ts # Sliding window rate limiter (per-entity, 10min windows)
|
|
29
|
+
│ └── register.ts # Tool registration helpers (toMcpResponse, logRegistered)
|
|
30
|
+
└── tools/
|
|
31
|
+
├── users.ts # Section 1: 13 user tools
|
|
32
|
+
├── agents.ts # Section 2: 3 agent tools
|
|
33
|
+
├── skills.ts # Section 3: 6 skill tools
|
|
34
|
+
├── tasks.ts # Section 4: 7 task tools (XP awarded here via RPC)
|
|
35
|
+
├── ratings.ts # Section 5: 7 rating tools
|
|
36
|
+
├── quests.ts # Section 6: 12 quest tools (NOT ACTIVE YET)
|
|
37
|
+
├── metrics.ts # Section 7: 3 metrics tools
|
|
38
|
+
├── xp.ts # Section 8: 5 XP/progression tools
|
|
39
|
+
├── leaderboards.ts # Section 9: 1 leaderboard tool
|
|
40
|
+
├── leveling.ts # Section 10: 3 leveling tools
|
|
41
|
+
├── achievements.ts # Section 11: 1 achievement tool
|
|
42
|
+
├── admin.ts # Section 12: 5 admin tools
|
|
43
|
+
└── system.ts # Section 13: 3 system tools (level-up check via RPC)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Core Data Flow
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
User asks Claude to do work
|
|
50
|
+
→ Claude calls levelup_infer_task_type (categorizes the work)
|
|
51
|
+
→ Claude calls levelup_start_task (creates task record)
|
|
52
|
+
→ Claude does the actual work
|
|
53
|
+
→ Claude calls levelup_complete_task (marks complete, triggers XP)
|
|
54
|
+
→ complete_task calls award_task_xp Postgres RPC function
|
|
55
|
+
→ RPC reads task_type.base_xp, applies difficulty/output multipliers
|
|
56
|
+
→ RPC applies completion curve
|
|
57
|
+
→ RPC splits XP according to xp_split_rules
|
|
58
|
+
→ RPC inserts task_xp_awards + xp_ledger + updates user/agent XP
|
|
59
|
+
→ Claude calls levelup_check_level_up (promotes if enough XP)
|
|
60
|
+
→ check_and_apply_level_up Postgres RPC function
|
|
61
|
+
→ Compares cumulative XP against level_curves
|
|
62
|
+
→ Updates level if threshold met
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Database — Key Tables
|
|
66
|
+
|
|
67
|
+
### User data
|
|
68
|
+
- `users` — Main profile (username, main_level, main_xp, profile_code, handle)
|
|
69
|
+
- `user_class_progress` — 3 rows per user (trainer, orchestrator, partner class levels)
|
|
70
|
+
- `user_identities` — Multi-platform identity linking
|
|
71
|
+
- `user_achievements` — Earned achievements per user
|
|
72
|
+
|
|
73
|
+
### Agent data
|
|
74
|
+
- `agents` — Agent profiles (name, level, xp, integrity_score, owner_user_id)
|
|
75
|
+
- `agent_skills` — Skills attached to agents with proficiency_level (0-10)
|
|
76
|
+
|
|
77
|
+
### Task/XP data
|
|
78
|
+
- `tasks` — Work records (user_id, agent_id, task_type_id, performer_type, status, difficulty, output_type)
|
|
79
|
+
- `task_types` — 82 task type definitions with base_xp and tier
|
|
80
|
+
- `task_xp_awards` — XP awarded per recipient per task
|
|
81
|
+
- `xp_ledger` — Full XP transaction history (the bank statement)
|
|
82
|
+
- `xp_split_rules` + `xp_split_rule_entries` — How XP is divided between participants
|
|
83
|
+
|
|
84
|
+
### Config/reference data
|
|
85
|
+
- `xp_config` — 58 configuration values (multipliers, bonuses, caps, gates)
|
|
86
|
+
- `level_curves` — XP required per level (30 levels x 2 entity types)
|
|
87
|
+
- `rank_definitions` — Rank names per level bracket (E through S)
|
|
88
|
+
- `completion_curves` — Exponential payout for partial completion
|
|
89
|
+
- `skill_categories` — 18 ability categories
|
|
90
|
+
- `skills` — 106 skills across abilities
|
|
91
|
+
- `achievement_definitions` — 54 achievement templates
|
|
92
|
+
|
|
93
|
+
## Performer Types
|
|
94
|
+
|
|
95
|
+
| performer_type | user_id | agent_id | XP Split |
|
|
96
|
+
|---------------|---------|----------|----------|
|
|
97
|
+
| agent_solo | null or owner | required | Agent 85%, User 15% |
|
|
98
|
+
| user_solo | required | null | User 100% |
|
|
99
|
+
| user_with_tools | required | null | User 100% |
|
|
100
|
+
| user_with_agent | required | required | Agent 60%, User 40% |
|
|
101
|
+
| orchestrated | required | required + additional | Agent 30%, Others 20% each, User 15% |
|
|
102
|
+
|
|
103
|
+
## Postgres RPC Functions (SECURITY DEFINER)
|
|
104
|
+
|
|
105
|
+
These bypass RLS and are the ONLY way to modify XP/level data:
|
|
106
|
+
|
|
107
|
+
1. `award_task_xp(p_task_id uuid)` — Recalculates and awards XP for a completed/failed task
|
|
108
|
+
2. `check_and_apply_level_up(p_entity_type text, p_entity_id uuid)` — Promotes entity if XP exceeds cumulative threshold
|
|
109
|
+
3. `grant_achievement(p_user_id uuid, p_achievement_key text)` — Validates and grants an achievement
|
|
110
|
+
4. `register_user_secure(...)` — Atomic user creation
|
|
111
|
+
5. `register_agent_secure(...)` — Agent creation with owner validation
|
|
112
|
+
|
|
113
|
+
## Current Status (as of March 14, 2026)
|
|
114
|
+
|
|
115
|
+
### Working
|
|
116
|
+
- All 69 MCP tools compile and register
|
|
117
|
+
- User registration, agent creation, skill management
|
|
118
|
+
- Task lifecycle (start → update → complete → fail)
|
|
119
|
+
- XP calculation via Postgres RPC (anti-cheat)
|
|
120
|
+
- Level-up via Postgres RPC (uses cumulative_xp correctly)
|
|
121
|
+
- Claude Desktop integration (stdio)
|
|
122
|
+
- OpenClaw tool registration (stdio)
|
|
123
|
+
- npm published (zero-config with anon key)
|
|
124
|
+
- Railway deployed (HTTP endpoint)
|
|
125
|
+
- Test suite: vitest with 8 test files (unit, integration, security, simulation)
|
|
126
|
+
|
|
127
|
+
### Known Bugs / Issues
|
|
128
|
+
|
|
129
|
+
**HIGH PRIORITY:**
|
|
130
|
+
1. **Agents not earning XP** — When Claude tracks tasks, it may not be passing agent_id to start_task, making them user_solo instead of user_with_agent. Check: is register_agent being called first? Is the agent_id being passed to start_task? Check task_xp_awards table to see performer_type and recipients.
|
|
131
|
+
|
|
132
|
+
2. **Stars not displayed** — Tool responses return rank_letter and rank_name but don't calculate star count. Fix: add `stars = (current_level - rank_min_level) + 1` to get_user_progress, get_agent_profile, and check_level_up responses.
|
|
133
|
+
|
|
134
|
+
3. **register_user and register_agent still do direct inserts** — Not yet refactored to use register_user_secure / register_agent_secure RPC. The Postgres functions exist but the TypeScript tool handlers still use direct supabase.from().insert(). Should call supabase.rpc() instead for consistency.
|
|
135
|
+
|
|
136
|
+
**MEDIUM PRIORITY:**
|
|
137
|
+
4. **Achievement triggers not automated** — grant_achievement RPC exists but nothing calls it automatically after task completion or level-up.
|
|
138
|
+
5. **Weekly bonus cron not set up** — 26 criteria in DB but no processing job.
|
|
139
|
+
6. **Dead code in fail_task** — Reads failConfig before calling awardTaskXp(task.id) but the RPC handles this internally.
|
|
140
|
+
|
|
141
|
+
**LOW PRIORITY:**
|
|
142
|
+
7. **supabase.ts comments reference service_role key** — Default is now anon key.
|
|
143
|
+
8. **xp_config has 58 entries vs 49 in seed doc** — 9 extra, should audit.
|
|
144
|
+
|
|
145
|
+
### Unactivated Features (designed but not connected)
|
|
146
|
+
|
|
147
|
+
| Feature | File | Issue |
|
|
148
|
+
|---------|------|-------|
|
|
149
|
+
| Skill discount not applied at level-up | system.ts:325 | `skill_discount_pct: 0` hardcoded; leveling.ts calculates it but never passes to RPC |
|
|
150
|
+
| Refinement progress = 0 | skills.ts:494 | `refinementProgress = 0; // TODO` — 1/3 of skill proficiency always zero |
|
|
151
|
+
| Tool installation has no XP reward | skills.ts:58-86 | Sets proficiency=1 on install but awards no XP |
|
|
152
|
+
| Tool installation table disconnected | skills.ts:465-470 | Reads `agent_tool_installations` but no code creates records |
|
|
153
|
+
| Integrity always +1 | ratings.ts:200 | Hardcoded `integrityDelta = 1` regardless of honesty |
|
|
154
|
+
| XP awarded before evaluation | tasks.ts:~260 | `complete_task` calls `award_task_xp` before any quality eval exists |
|
|
155
|
+
| xp_config not seeded | system.ts, tasks.ts | Cooldowns silently disabled if DB entries missing |
|
|
156
|
+
| LLM task evaluation | ratings.ts:291 | Returns "deferred" string |
|
|
157
|
+
| Email OTP sending | users.ts:513 | TODO: no mail service |
|
|
158
|
+
| Quest tools | quests.ts | Not active yet — all 12 quest tools deferred |
|
|
159
|
+
|
|
160
|
+
## XP Formula
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
Total XP Pool = Base XP x Difficulty Multiplier x Output Multiplier x Completion Payout
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Difficulty multipliers: 1→0.5, 2→0.7, 3→1.0, 4→1.3, 5→1.6, 6→2.0, 7→2.5
|
|
167
|
+
Output multipliers: conversational→1.0, deliverable→1.75, deployed→2.5
|
|
168
|
+
Failed tasks: 15% of calculated XP
|
|
169
|
+
Cancelled tasks: 0 XP
|
|
170
|
+
|
|
171
|
+
## Conventions
|
|
172
|
+
|
|
173
|
+
- All tool names prefixed with `levelup_`
|
|
174
|
+
- Tool handlers return `toMcpResponse(ok({...}))` for success, `toMcpResponse(fail("message"))` for errors
|
|
175
|
+
- Supabase queries use `.maybeSingle()` for optional results, `.single()` for required
|
|
176
|
+
- UUIDs validated via Zod `uuidSchema`
|
|
177
|
+
- Pagination via `limitSchema` (default 20) + `offsetSchema` (default 0)
|
|
178
|
+
|
|
179
|
+
## Build & Run
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm run build # Compile TypeScript → dist/
|
|
183
|
+
npm start # Run in stdio mode (default)
|
|
184
|
+
npm run start:http # Run in HTTP mode on port 3000
|
|
185
|
+
npm run dev # Watch mode for development
|
|
186
|
+
npm test # Run vitest test suite (uses --pool forks)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Testing
|
|
190
|
+
|
|
191
|
+
### Unit & simulation tests (vitest)
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npm test # Runs all test files
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Test files:
|
|
198
|
+
- `src/services/format.test.ts` — 16 tests for formatting helpers
|
|
199
|
+
- `src/services/errors.test.ts` — 12 tests for error handling
|
|
200
|
+
- `src/services/rate-limit.test.ts` — 7 tests for sliding window rate limiter
|
|
201
|
+
- `src/tools/tasks.test.ts` — 10 integration tests for task RPCs
|
|
202
|
+
- `src/tools/security.test.ts` — 36 security tests (RPC whitelisting, anti-farming, input validation)
|
|
203
|
+
- `src/tools/simulation.test.ts` — 28 tests simulating legitimate + attack scenarios with in-memory mock DB
|
|
204
|
+
- `src/tools/timing-simulation.test.ts` — 10 tests for XP tier timing boundaries
|
|
205
|
+
- `src/tools/progression-simulation.test.ts` — 6 tests analyzing level curve and farming strategies
|
|
206
|
+
|
|
207
|
+
Note: Uses `pool: "forks"` in vitest.config.ts (threads pool causes module isolation crashes on Windows/OneDrive). The `pretest` script clears `.vite` cache to prevent corruption.
|
|
208
|
+
|
|
209
|
+
### HTTP endpoint testing
|
|
210
|
+
|
|
211
|
+
Start HTTP server: `TRANSPORT=http PORT=3000 node dist/index.js`
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Health check
|
|
215
|
+
curl http://localhost:3000/health
|
|
216
|
+
|
|
217
|
+
# Call a tool
|
|
218
|
+
curl -X POST http://localhost:3000/mcp \
|
|
219
|
+
-H "Content-Type: application/json" \
|
|
220
|
+
-H "Accept: application/json, text/event-stream" \
|
|
221
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"levelup_list_task_types","arguments":{}}}'
|
|
222
|
+
```
|
package/CODE_REVIEW.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Code Review: Level-Up MCP Server
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-03-14
|
|
4
|
+
**Branch:** `review/code-review`
|
|
5
|
+
**Reviewer:** Claude (automated)
|
|
6
|
+
**Repo:** https://github.com/klexian/level-up-mcp-server
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Well-architected gamification MCP server with strong TypeScript patterns, clean error handling, and good separation of concerns. The project has **3 critical**, **4 major**, and **7 minor** issues alongside numerous strengths.
|
|
13
|
+
|
|
14
|
+
**Overall Grade: B+** (7.5/10 — can reach 9+/10 with fixes below)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Critical Issues
|
|
19
|
+
|
|
20
|
+
### C1. Hardcoded Supabase credentials in source code
|
|
21
|
+
|
|
22
|
+
**File:** `src/services/supabase.ts:30-31`
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
const supabaseUrl = process.env.SUPABASE_URL || "https://pxanrmpwptinbtwigqdt.supabase.co";
|
|
26
|
+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY || "eyJhbGciOiJIUzI1...";
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The Supabase URL and an anon JWT are hardcoded as fallbacks. Anyone with repo access can see these. Even though the fallback is an anon key (not the service_role key), it still exposes the project URL and an auth token.
|
|
30
|
+
|
|
31
|
+
**Fix:** Remove the fallback strings. Fail at startup if env vars are missing (the `validateEnv()` function already exists but the fallbacks prevent it from ever triggering).
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const supabaseUrl = process.env.SUPABASE_URL || "";
|
|
35
|
+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY || "";
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### C2. OTP code stored and returned in plaintext
|
|
41
|
+
|
|
42
|
+
**File:** `src/tools/users.ts:477-493`
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const otpCode = String(Math.floor(100000 + Math.random() * 900000));
|
|
46
|
+
// ...
|
|
47
|
+
code_hash: otpCode, // ← Not actually hashed despite column name
|
|
48
|
+
// ...
|
|
49
|
+
_debug_code: otpCode, // ← Returned to caller!
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The OTP is stored unencrypted (despite the column being named `code_hash`) and returned in the API response via `_debug_code`. The comment says "Remove in production!" but it's currently live.
|
|
53
|
+
|
|
54
|
+
**Fix:**
|
|
55
|
+
1. Hash the OTP before storage (e.g., SHA-256)
|
|
56
|
+
2. Remove `_debug_code` from the response
|
|
57
|
+
3. Use `crypto.randomInt()` instead of `Math.random()` for cryptographic safety
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### C3. Merge profiles skips verification
|
|
62
|
+
|
|
63
|
+
**File:** `src/tools/users.ts:819`
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// TODO: Proper verification_token validation
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`levelup_merge_profiles` is a destructive operation (deactivates a user account, moves all assets) but the `verification_token` parameter is never actually checked. Any caller can merge any two accounts.
|
|
70
|
+
|
|
71
|
+
**Fix:** Validate the token against the link_codes or otp_codes table before proceeding.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Major Issues
|
|
76
|
+
|
|
77
|
+
### M1. No rate limiting on any tools
|
|
78
|
+
|
|
79
|
+
**Scope:** All 69 tools
|
|
80
|
+
|
|
81
|
+
Any caller can invoke tools unlimited times. This enables XP farming (create many tasks, immediately complete them). The `award_task_xp` RPC provides some server-side validation, but doesn't prevent volume abuse.
|
|
82
|
+
|
|
83
|
+
**Recommendation:** Add per-entity rate limits, especially on `levelup_start_task`, `levelup_complete_task`, and `levelup_complete_quest`.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### M2. Race condition in XP ledger (quests)
|
|
88
|
+
|
|
89
|
+
**File:** `src/tools/quests.ts:83-96` and `src/tools/quests.ts:540-549`
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const { data: lastEntry } = await supabase
|
|
93
|
+
.from("xp_ledger").select("running_total")
|
|
94
|
+
.eq("entity_type", entityType).eq("entity_id", entityId)
|
|
95
|
+
.order("created_at", { ascending: false }).limit(1).maybeSingle();
|
|
96
|
+
|
|
97
|
+
await supabase.from("xp_ledger").insert({
|
|
98
|
+
// ...
|
|
99
|
+
running_total: (lastEntry?.running_total || 0) + xpAmount,
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The running total is read-then-written in application code without a transaction lock. Two simultaneous quest completions for the same entity could produce an incorrect running total.
|
|
104
|
+
|
|
105
|
+
**Note:** Task XP correctly uses the `award_task_xp` RPC (atomic, server-side). Quest and weekly bonus XP should follow the same pattern.
|
|
106
|
+
|
|
107
|
+
**Fix:** Create a Postgres RPC function for quest/bonus XP awards, similar to `award_task_xp`.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### M3. Leaderboard shows all users (no privacy filter)
|
|
112
|
+
|
|
113
|
+
**File:** `src/tools/leaderboards.ts:98-102`
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const { data: users } = await supabase
|
|
117
|
+
.from("users")
|
|
118
|
+
.select("id, username, handle, profile_code, main_xp")
|
|
119
|
+
.order("main_xp", { ascending: false })
|
|
120
|
+
// No filter for private/hidden users
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Users who set `is_searchable_by_email: false` or auto-created "Anonymous" accounts still appear in leaderboard rankings.
|
|
124
|
+
|
|
125
|
+
**Fix:** Add a filter for visibility, or at minimum exclude auto-created anonymous accounts:
|
|
126
|
+
```typescript
|
|
127
|
+
.neq("username", "Anonymous")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### M4. Community quest XP deposit not atomic
|
|
133
|
+
|
|
134
|
+
**File:** `src/tools/quests.ts:643-655`
|
|
135
|
+
|
|
136
|
+
The XP deduction for community quest creation reads the user's XP, then updates it in two separate queries. A race condition could allow spending more XP than available.
|
|
137
|
+
|
|
138
|
+
**Fix:** Use a Postgres function with `FOR UPDATE` locking, or an RPC call.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Minor Issues
|
|
143
|
+
|
|
144
|
+
### m1. N+1 query pattern in leaderboards
|
|
145
|
+
|
|
146
|
+
**File:** `src/tools/leaderboards.ts:78-92`
|
|
147
|
+
|
|
148
|
+
Class leaderboards make one DB query per row to fetch user info:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const entries = await Promise.all(
|
|
152
|
+
(classRows || []).map(async (row, idx) => {
|
|
153
|
+
const { data: user } = await supabase
|
|
154
|
+
.from("users").select("username, handle, profile_code")
|
|
155
|
+
.eq("id", row.user_id).single(); // One query per row
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
With 25 rows (default limit), this makes 25 extra queries. Same pattern appears in agent skill leaderboard (lines 161-176) and `list_user_agents` (lines 324-333 in agents.ts).
|
|
159
|
+
|
|
160
|
+
**Fix:** Batch-fetch with `.in("id", userIds)` and build a lookup Map.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### m2. No array size limits on inputs
|
|
165
|
+
|
|
166
|
+
**File:** `src/tools/tasks.ts:110`
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
additional_agent_ids: z.array(uuidSchema).optional()
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
No `.max()` constraint — a caller could pass thousands of UUIDs. Same issue with `criteria` array in `src/tools/quests.ts:178`.
|
|
173
|
+
|
|
174
|
+
**Fix:** Add `.max(10)` or similar reasonable limits to all array schemas.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### m3. SELECT * used where not needed
|
|
179
|
+
|
|
180
|
+
**Files:**
|
|
181
|
+
- `src/tools/agents.ts:191` — `get_agent_profile` fetches all columns including potentially large metadata
|
|
182
|
+
- `src/tools/tasks.ts:260` — `complete_task` fetches full task row
|
|
183
|
+
- `src/tools/quests.ts:45` — `process_weekly_bonuses` fetches all criteria columns
|
|
184
|
+
|
|
185
|
+
**Fix:** Select only needed columns.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### m4. Username field not sanitized
|
|
190
|
+
|
|
191
|
+
**File:** `src/tools/users.ts:103`
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
username: z.string().min(1).max(100)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Accepts any characters including control characters, null bytes, HTML, RTL text. The `handle` field correctly uses a regex (`/^[a-z0-9_]+$/`) but `username` does not.
|
|
198
|
+
|
|
199
|
+
**Fix:** Add a regex or `.trim()` at minimum.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### m5. PORT validation missing
|
|
204
|
+
|
|
205
|
+
**File:** `src/index.ts:150`
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const port = parseInt(process.env.PORT || "3000", 10);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
If `PORT="abc"`, `parseInt` returns `NaN` and the server starts on an invalid port silently.
|
|
212
|
+
|
|
213
|
+
**Fix:** Validate the parsed port is a number in valid range.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### m6. No test suite
|
|
218
|
+
|
|
219
|
+
No test files, no test framework (jest/vitest) in dependencies or config. For a project with 69 tools and complex XP calculation logic, automated tests are important.
|
|
220
|
+
|
|
221
|
+
**Recommendation:** Start with integration tests for the core flow: `start_task` → `update_task` → `complete_task` → verify XP awarded.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### m7. Metadata accepts arbitrary JSON
|
|
226
|
+
|
|
227
|
+
**Files:** Multiple tools via `metadataSchema` in `src/schemas/common.ts`
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
export const metadataSchema = z.record(z.unknown()).optional();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
While stored safely as JSONB in Postgres, there's no size limit or depth restriction. A caller could send a deeply nested or very large JSON payload.
|
|
234
|
+
|
|
235
|
+
**Fix:** Add `.refine()` to limit serialized size, e.g.:
|
|
236
|
+
```typescript
|
|
237
|
+
metadataSchema = z.record(z.unknown()).optional()
|
|
238
|
+
.refine(val => !val || JSON.stringify(val).length < 10000, "Metadata too large");
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Strengths
|
|
244
|
+
|
|
245
|
+
| Area | Detail |
|
|
246
|
+
|------|--------|
|
|
247
|
+
| **Type safety** | `strict: true` in tsconfig, comprehensive interfaces in `types.ts`, Zod schemas for all inputs |
|
|
248
|
+
| **Error handling** | Consistent `ok()`/`fail()` envelope pattern via `services/errors.ts`, `withErrorHandling()` HOF |
|
|
249
|
+
| **Code organization** | 13 tool files, shared services/schemas, each section registers independently |
|
|
250
|
+
| **MCP compliance** | Proper annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`), rich tool descriptions |
|
|
251
|
+
| **Anti-cheat design** | `award_task_xp` uses Postgres RPC (SECURITY DEFINER), weekly bonuses hide criteria names |
|
|
252
|
+
| **Privacy** | Email only shown to opted-in users, identity IDs never exposed in profiles |
|
|
253
|
+
| **Comments** | Thorough header blocks and inline comments explaining design decisions |
|
|
254
|
+
| **Dependencies** | Minimal and up-to-date (MCP SDK, Supabase, Express v5, Zod) |
|
|
255
|
+
| **Handle validation** | Correctly enforced with regex `/^[a-z0-9_]+$/` |
|
|
256
|
+
| **Git hygiene** | `.env` properly in `.gitignore`, never committed to history |
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Recommended Priority
|
|
261
|
+
|
|
262
|
+
### Immediate
|
|
263
|
+
- [ ] **C1** — Remove hardcoded credentials from `supabase.ts`
|
|
264
|
+
- [ ] **C2** — Hash OTP codes, remove `_debug_code` from response
|
|
265
|
+
- [ ] **C3** — Implement verification token validation in `merge_profiles`
|
|
266
|
+
|
|
267
|
+
### High Priority
|
|
268
|
+
- [ ] **M1** — Add rate limiting (at least on XP-generating tools)
|
|
269
|
+
- [ ] **M2** — Move quest/bonus XP ledger writes to Postgres RPC
|
|
270
|
+
- [ ] **M3** — Add privacy filter to leaderboard queries
|
|
271
|
+
- [ ] **M4** — Make XP deposit deduction atomic
|
|
272
|
+
|
|
273
|
+
### Medium Priority
|
|
274
|
+
- [ ] **m1** — Fix N+1 queries in leaderboards and agent listings
|
|
275
|
+
- [ ] **m2** — Add max array size limits to schemas
|
|
276
|
+
- [ ] **m4** — Sanitize username field
|
|
277
|
+
- [ ] **m6** — Set up test framework and write core flow tests
|
|
278
|
+
|
|
279
|
+
### Low Priority
|
|
280
|
+
- [ ] **m3** — Replace SELECT * with explicit column lists
|
|
281
|
+
- [ ] **m5** — Validate PORT env var
|
|
282
|
+
- [ ] **m7** — Add metadata size limits
|
package/LICENSE
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Parameters
|
|
4
|
+
|
|
5
|
+
Licensor: Ken (klexian)
|
|
6
|
+
Licensed Work: Level-Up MCP Server
|
|
7
|
+
The Licensed Work is (c) 2026 Ken (klexian)
|
|
8
|
+
Additional Use Grant: You may use the Licensed Work for any purpose, including
|
|
9
|
+
production use, EXCEPT for offering a competing
|
|
10
|
+
gamification-as-a-service product. A "competing product"
|
|
11
|
+
means any product or service that provides XP tracking,
|
|
12
|
+
leveling, skill progression, or quest/achievement systems
|
|
13
|
+
for AI agents or human-agent collaboration as its primary
|
|
14
|
+
function.
|
|
15
|
+
Change Date: March 13, 2030
|
|
16
|
+
Change License: Apache License, Version 2.0
|
|
17
|
+
|
|
18
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
19
|
+
please contact the Licensor.
|
|
20
|
+
|
|
21
|
+
Notice
|
|
22
|
+
|
|
23
|
+
Business Source License 1.1
|
|
24
|
+
|
|
25
|
+
Terms
|
|
26
|
+
|
|
27
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
28
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
29
|
+
Licensor may make an Additional Use Grant, above, permitting limited production
|
|
30
|
+
use.
|
|
31
|
+
|
|
32
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
33
|
+
available distribution of a specific version of the Licensed Work under this
|
|
34
|
+
License, whichever comes first, the Licensor hereby grants you rights under the
|
|
35
|
+
terms of the Change License, and the rights granted in the paragraph above
|
|
36
|
+
terminate.
|
|
37
|
+
|
|
38
|
+
If your use of the Licensed Work does not comply with the requirements currently
|
|
39
|
+
in effect as described in this License, you must purchase a commercial license
|
|
40
|
+
from the Licensor, its affiliated entities, or authorized resellers, or you must
|
|
41
|
+
refrain from using the Licensed Work.
|
|
42
|
+
|
|
43
|
+
All copies of the original and modified Licensed Work, and derivative works of
|
|
44
|
+
the Licensed Work, are subject to this License. This License applies separately
|
|
45
|
+
for each version of the Licensed Work and the Change Date may vary for each
|
|
46
|
+
version of the Licensed Work released by Licensor.
|
|
47
|
+
|
|
48
|
+
You must conspicuously display this License on each original or modified copy of
|
|
49
|
+
the Licensed Work. If you receive the Licensed Work in original or modified form
|
|
50
|
+
from a third party, the terms and conditions set forth in this License apply to
|
|
51
|
+
your use of that work.
|
|
52
|
+
|
|
53
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
54
|
+
terminate your rights under this License for the current and all other versions
|
|
55
|
+
of the Licensed Work.
|
|
56
|
+
|
|
57
|
+
This License does not grant you any right in any trademark or logo of Licensor
|
|
58
|
+
or its affiliates (provided that you may use a trademark or logo of Licensor as
|
|
59
|
+
expressly required by this License).
|
|
60
|
+
|
|
61
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN
|
|
62
|
+
"AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS
|
|
63
|
+
OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY,
|
|
64
|
+
FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE.
|