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.
Files changed (137) hide show
  1. package/.claude/projects/c--Users-klexi-OneDrive-Desktop-Levelup-level-up-mcp-server/memory/project_testing_service.md +11 -0
  2. package/.claude/settings.local.json +10 -0
  3. package/.env.example +19 -0
  4. package/CLAUDE.md +222 -0
  5. package/CODE_REVIEW.md +282 -0
  6. package/LICENSE +64 -0
  7. package/README.md +198 -0
  8. package/dist/constants.d.ts +33 -0
  9. package/dist/constants.js +78 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/data/quest-seeds.d.ts +18 -0
  12. package/dist/data/quest-seeds.js +380 -0
  13. package/dist/data/quest-seeds.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +260 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/schemas/common.d.ts +33 -0
  18. package/dist/schemas/common.js +96 -0
  19. package/dist/schemas/common.js.map +1 -0
  20. package/dist/services/dispatcher.d.ts +27 -0
  21. package/dist/services/dispatcher.js +47 -0
  22. package/dist/services/dispatcher.js.map +1 -0
  23. package/dist/services/errors.d.ts +56 -0
  24. package/dist/services/errors.js +99 -0
  25. package/dist/services/errors.js.map +1 -0
  26. package/dist/services/format.d.ts +74 -0
  27. package/dist/services/format.js +144 -0
  28. package/dist/services/format.js.map +1 -0
  29. package/dist/services/ownership.d.ts +19 -0
  30. package/dist/services/ownership.js +79 -0
  31. package/dist/services/ownership.js.map +1 -0
  32. package/dist/services/quality-gate.d.ts +45 -0
  33. package/dist/services/quality-gate.js +131 -0
  34. package/dist/services/quality-gate.js.map +1 -0
  35. package/dist/services/rate-limit.d.ts +12 -0
  36. package/dist/services/rate-limit.js +49 -0
  37. package/dist/services/rate-limit.js.map +1 -0
  38. package/dist/services/register.d.ts +49 -0
  39. package/dist/services/register.js +63 -0
  40. package/dist/services/register.js.map +1 -0
  41. package/dist/services/supabase.d.ts +10 -0
  42. package/dist/services/supabase.js +79 -0
  43. package/dist/services/supabase.js.map +1 -0
  44. package/dist/tools/achievements.d.ts +6 -0
  45. package/dist/tools/achievements.js +242 -0
  46. package/dist/tools/achievements.js.map +1 -0
  47. package/dist/tools/admin.d.ts +16 -0
  48. package/dist/tools/admin.js +328 -0
  49. package/dist/tools/admin.js.map +1 -0
  50. package/dist/tools/agents.d.ts +3 -0
  51. package/dist/tools/agents.js +400 -0
  52. package/dist/tools/agents.js.map +1 -0
  53. package/dist/tools/bootstrap.d.ts +17 -0
  54. package/dist/tools/bootstrap.js +565 -0
  55. package/dist/tools/bootstrap.js.map +1 -0
  56. package/dist/tools/dispatchers/admin.d.ts +3 -0
  57. package/dist/tools/dispatchers/admin.js +50 -0
  58. package/dist/tools/dispatchers/admin.js.map +1 -0
  59. package/dist/tools/dispatchers/eval.d.ts +3 -0
  60. package/dist/tools/dispatchers/eval.js +40 -0
  61. package/dist/tools/dispatchers/eval.js.map +1 -0
  62. package/dist/tools/dispatchers/quests.d.ts +3 -0
  63. package/dist/tools/dispatchers/quests.js +60 -0
  64. package/dist/tools/dispatchers/quests.js.map +1 -0
  65. package/dist/tools/dispatchers/session.d.ts +3 -0
  66. package/dist/tools/dispatchers/session.js +38 -0
  67. package/dist/tools/dispatchers/session.js.map +1 -0
  68. package/dist/tools/dispatchers/skills.d.ts +3 -0
  69. package/dist/tools/dispatchers/skills.js +49 -0
  70. package/dist/tools/dispatchers/skills.js.map +1 -0
  71. package/dist/tools/dispatchers/tasks.d.ts +3 -0
  72. package/dist/tools/dispatchers/tasks.js +53 -0
  73. package/dist/tools/dispatchers/tasks.js.map +1 -0
  74. package/dist/tools/dispatchers/users.d.ts +3 -0
  75. package/dist/tools/dispatchers/users.js +65 -0
  76. package/dist/tools/dispatchers/users.js.map +1 -0
  77. package/dist/tools/dispatchers/xp.d.ts +3 -0
  78. package/dist/tools/dispatchers/xp.js +51 -0
  79. package/dist/tools/dispatchers/xp.js.map +1 -0
  80. package/dist/tools/growth-plan.d.ts +5 -0
  81. package/dist/tools/growth-plan.js +791 -0
  82. package/dist/tools/growth-plan.js.map +1 -0
  83. package/dist/tools/leaderboards.d.ts +10 -0
  84. package/dist/tools/leaderboards.js +279 -0
  85. package/dist/tools/leaderboards.js.map +1 -0
  86. package/dist/tools/leveling.d.ts +24 -0
  87. package/dist/tools/leveling.js +356 -0
  88. package/dist/tools/leveling.js.map +1 -0
  89. package/dist/tools/metrics.d.ts +3 -0
  90. package/dist/tools/metrics.js +247 -0
  91. package/dist/tools/metrics.js.map +1 -0
  92. package/dist/tools/quests.d.ts +5 -0
  93. package/dist/tools/quests.js +586 -0
  94. package/dist/tools/quests.js.map +1 -0
  95. package/dist/tools/ratings.d.ts +11 -0
  96. package/dist/tools/ratings.js +564 -0
  97. package/dist/tools/ratings.js.map +1 -0
  98. package/dist/tools/skills.d.ts +66 -0
  99. package/dist/tools/skills.js +1112 -0
  100. package/dist/tools/skills.js.map +1 -0
  101. package/dist/tools/system.d.ts +31 -0
  102. package/dist/tools/system.js +605 -0
  103. package/dist/tools/system.js.map +1 -0
  104. package/dist/tools/tasks.d.ts +73 -0
  105. package/dist/tools/tasks.js +1572 -0
  106. package/dist/tools/tasks.js.map +1 -0
  107. package/dist/tools/users.d.ts +97 -0
  108. package/dist/tools/users.js +1306 -0
  109. package/dist/tools/users.js.map +1 -0
  110. package/dist/tools/xp.d.ts +38 -0
  111. package/dist/tools/xp.js +670 -0
  112. package/dist/tools/xp.js.map +1 -0
  113. package/dist/types.d.ts +178 -0
  114. package/dist/types.js +12 -0
  115. package/dist/types.js.map +1 -0
  116. package/docs/recommended-skillsets.md +622 -0
  117. package/docs/skills-and-abilities-review.md +672 -0
  118. package/docs/v0.3-roadmap.md +191 -0
  119. package/package.json +35 -0
  120. package/sql/agent_pending_installs.sql +28 -0
  121. package/sql/award_class_xp.sql +81 -0
  122. package/supabase/.temp/cli-latest +1 -0
  123. package/supabase/.temp/gotrue-version +1 -0
  124. package/supabase/.temp/pooler-url +1 -0
  125. package/supabase/.temp/postgres-version +1 -0
  126. package/supabase/.temp/project-ref +1 -0
  127. package/supabase/.temp/rest-version +1 -0
  128. package/supabase/.temp/storage-migration +1 -0
  129. package/supabase/.temp/storage-version +1 -0
  130. package/supabase/migrations/20260314000000_anon_rls_policies.sql +311 -0
  131. package/supabase/migrations/20260314000001_ownership_rpcs.sql +382 -0
  132. package/supabase/migrations/20260314000002_evidence_and_growth_plan.sql +97 -0
  133. package/supabase/migrations/20260317000000_seed_quests.sql +62 -0
  134. package/supabase/migrations/20260317000001_star_cooldown_and_fixes.sql +16 -0
  135. package/supabase/migrations/20260318000000_restore_rank_names.sql +25 -0
  136. package/supabase/migrations/20260320000000_chinese_rank_names.sql +24 -0
  137. 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.
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm run:*)",
5
+ "Bash(git add:*)",
6
+ "Bash(git commit:*)",
7
+ "mcp__claude_ai_Supabase__apply_migration"
8
+ ]
9
+ }
10
+ }
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.