@hoya25/nctr-mcp-server 1.0.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/.mcp/server.json +26 -0
- package/LICENSE +21 -0
- package/README.md +137 -0
- package/package.json +29 -0
- package/supabase/config.toml +2 -0
- package/supabase/functions/mcp/deno.json +7 -0
- package/supabase/functions/mcp/index.ts +897 -0
- package/test-mcp-server.sh +197 -0
package/.mcp/server.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.hoya25/nctr-mcp-server",
|
|
4
|
+
"description": "NCTR Alliance rewards program — search bounties, check earning rates by tier, discover Impact Engine communities, and get onboarding links. The first rewards and loyalty MCP server.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/Hoya25/mcp-server",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "@hoya25/nctr-mcp-server",
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"remotes": [
|
|
21
|
+
{
|
|
22
|
+
"transportType": "streamable-http",
|
|
23
|
+
"url": "https://yhwcaodofmbusjurawhp.supabase.co/functions/v1/mcp/rpc"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NCTR Alliance
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# NCTR Alliance MCP Server
|
|
2
|
+
|
|
3
|
+
**The first rewards and loyalty MCP server.** Enables AI agents to discover and interact with the NCTR Alliance rewards program — bounties, earning rates, tier progression, promotions, and Impact Engine communities.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
When someone asks an AI assistant about NCTR, the assistant can call this server to get real, structured data instead of guessing. The server exposes 6 tools:
|
|
8
|
+
|
|
9
|
+
| Tool | What It Does |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
| `search_bounties` | Search and filter all 19 available bounties by category, amount, keyword, or repeatability |
|
|
12
|
+
| `get_earning_rates` | Get earning multipliers by Crescendo tier, with optional bounty calculations |
|
|
13
|
+
| `check_tier_requirements` | Check tier thresholds, perks, and progress toward the next tier |
|
|
14
|
+
| `get_active_promotions` | Get current limited-time offers and launch bonuses |
|
|
15
|
+
| `get_onboarding_link` | Generate a join link with optional referral code |
|
|
16
|
+
| `get_impact_engines` | Discover NCTR's 6 passion-based communities |
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Connect to the hosted server
|
|
21
|
+
|
|
22
|
+
Add this to your MCP client configuration (Claude Desktop, Cursor, etc.):
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"nctr-alliance": {
|
|
28
|
+
"url": "https://yhwcaodofmbusjurawhp.supabase.co/functions/v1/mcp/rpc"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Claude Code (terminal)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
claude mcp add nctr-alliance -t http https://yhwcaodofmbusjurawhp.supabase.co/functions/v1/mcp/rpc
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Or run locally
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone https://github.com/Hoya25/mcp-server.git
|
|
44
|
+
cd mcp-server
|
|
45
|
+
supabase start
|
|
46
|
+
supabase functions serve --no-verify-jwt mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Server runs at: `http://localhost:54321/functions/v1/mcp/rpc`
|
|
50
|
+
|
|
51
|
+
## Example Queries
|
|
52
|
+
|
|
53
|
+
Once connected, you can ask your AI assistant things like:
|
|
54
|
+
|
|
55
|
+
- "What bounties can I earn with NCTR?"
|
|
56
|
+
- "How much NCTR do I get for referring a friend?"
|
|
57
|
+
- "What tier am I at with 3,000 NCTR?"
|
|
58
|
+
- "What's the earning multiplier at Gold tier?"
|
|
59
|
+
- "Are there any active promotions?"
|
|
60
|
+
- "Tell me about the NCTR Impact Engines"
|
|
61
|
+
|
|
62
|
+
## How Bounties Work
|
|
63
|
+
|
|
64
|
+
Every bounty in NCTR uses **360LOCK** — tokens are locked for exactly 360 days. They're committed, not spent. After the lock period, the NCTR is fully yours.
|
|
65
|
+
|
|
66
|
+
### Bounty Categories
|
|
67
|
+
|
|
68
|
+
| Category | Examples |
|
|
69
|
+
|----------|---------|
|
|
70
|
+
| **Entry** | Sign-up (625), Early Adopter Bonus (1,250), Profile Completion (375) |
|
|
71
|
+
| **Revenue** | First Purchase (2,500), Shop & Earn (250 uncapped), Quarterly Spend (5,000) |
|
|
72
|
+
| **Merch** | Collection (5,000), Repeat (500) |
|
|
73
|
+
| **Referral** | Friend sign-ups (625), friend purchases (2,500), milestone bonuses |
|
|
74
|
+
| **Engagement** | Social shares (500), monthly activity (250), community contributions (2,000) |
|
|
75
|
+
|
|
76
|
+
### Crescendo Status Tiers
|
|
77
|
+
|
|
78
|
+
| Tier | NCTR Required | Multiplier |
|
|
79
|
+
|------|--------------|------------|
|
|
80
|
+
| Bronze | 0 | 1.0x |
|
|
81
|
+
| Silver | 1,000 | 1.2x |
|
|
82
|
+
| Gold | 5,000 | 1.5x |
|
|
83
|
+
| Platinum | 25,000 | 1.8x |
|
|
84
|
+
| Diamond | 100,000 | 2.0x |
|
|
85
|
+
|
|
86
|
+
## Impact Engines
|
|
87
|
+
|
|
88
|
+
NCTR includes 6 passion-based communities called Impact Engines:
|
|
89
|
+
|
|
90
|
+
- **THROTTLE** — Powersports
|
|
91
|
+
- **GROUNDBALL** — Lacrosse
|
|
92
|
+
- **STARDUST** — Entertainment
|
|
93
|
+
- **SWEAT** — Skilled Trades
|
|
94
|
+
- **SISU** — Recovery
|
|
95
|
+
- **INSPIRATION** — Wellness
|
|
96
|
+
|
|
97
|
+
## API Endpoints
|
|
98
|
+
|
|
99
|
+
| Endpoint | Method | Description |
|
|
100
|
+
|----------|--------|-------------|
|
|
101
|
+
| `/` | GET | Server info and tool list |
|
|
102
|
+
| `/health` | GET | Health check |
|
|
103
|
+
| `/rpc` | POST | MCP protocol endpoint (Streamable HTTP) |
|
|
104
|
+
|
|
105
|
+
### Test with curl
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Health check
|
|
109
|
+
curl https://yhwcaodofmbusjurawhp.supabase.co/functions/v1/mcp/health
|
|
110
|
+
|
|
111
|
+
# Search all bounties
|
|
112
|
+
curl -X POST https://yhwcaodofmbusjurawhp.supabase.co/functions/v1/mcp/rpc \
|
|
113
|
+
-H "Content-Type: application/json" \
|
|
114
|
+
-H "Accept: application/json, text/event-stream" \
|
|
115
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_bounties","arguments":{}}}'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Deployment
|
|
119
|
+
|
|
120
|
+
This server runs on **Supabase Edge Functions** using the official MCP TypeScript SDK (`@modelcontextprotocol/sdk@1.25.3`).
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
supabase link --project-ref yhwcaodofmbusjurawhp
|
|
124
|
+
supabase functions deploy --no-verify-jwt mcp
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Links
|
|
128
|
+
|
|
129
|
+
- **NCTR Alliance**: [nctr.live](https://nctr.live)
|
|
130
|
+
- **The Garden** (shopping): [thegarden.nctr.live](https://thegarden.nctr.live)
|
|
131
|
+
- **Crescendo** (onboarding): [crescendo.nctr.live](https://crescendo.nctr.live)
|
|
132
|
+
- **For Agents**: [thegarden.nctr.live/for-agents](https://thegarden.nctr.live/for-agents)
|
|
133
|
+
- **Manifest**: [nctr.live/.well-known/nctr.json](https://nctr.live/.well-known/nctr.json)
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hoya25/nctr-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for the NCTR Alliance rewards program. Enables AI agents to discover bounties, earning rates, tier requirements, promotions, and Impact Engine communities.",
|
|
5
|
+
"mcpName": "io.github.hoya25/nctr-mcp-server",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Hoya25/mcp-server.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"mcp",
|
|
12
|
+
"rewards",
|
|
13
|
+
"loyalty",
|
|
14
|
+
"nctr",
|
|
15
|
+
"bounties",
|
|
16
|
+
"model-context-protocol"
|
|
17
|
+
],
|
|
18
|
+
"author": "NCTR Alliance <anderson@butterflystudios.live>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20.0.0"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "supabase functions serve --no-verify-jwt mcp",
|
|
25
|
+
"deploy": "supabase functions deploy --no-verify-jwt mcp",
|
|
26
|
+
"test": "bash test-mcp-server.sh",
|
|
27
|
+
"inspector": "npx @modelcontextprotocol/inspector"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NCTR Alliance MCP Server
|
|
3
|
+
* ========================
|
|
4
|
+
* Model Context Protocol server for AI agents to discover and interact
|
|
5
|
+
* with the NCTR rewards program, bounties, and Impact Engines.
|
|
6
|
+
*
|
|
7
|
+
* Deployment: Supabase Edge Functions
|
|
8
|
+
* Transport: Streamable HTTP (MCP specification standard)
|
|
9
|
+
* Auth: Public (no authentication required)
|
|
10
|
+
*
|
|
11
|
+
* Tools:
|
|
12
|
+
* 1. search_bounties — Search and filter available bounties
|
|
13
|
+
* 2. get_earning_rates — Get earning rates by Crescendo tier
|
|
14
|
+
* 3. check_tier_requirements — Check requirements for each tier
|
|
15
|
+
* 4. get_active_promotions — Get current promotions and limited offers
|
|
16
|
+
* 5. get_onboarding_link — Generate a member onboarding link
|
|
17
|
+
* 6. get_impact_engines — Discover NCTR Impact Engine communities
|
|
18
|
+
*
|
|
19
|
+
* NCTR Alliance — https://nctr.live
|
|
20
|
+
* The Garden — https://thegarden.nctr.live
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
|
|
24
|
+
|
|
25
|
+
import { McpServer } from 'npm:@modelcontextprotocol/sdk@1.25.3/server/mcp.js'
|
|
26
|
+
import { WebStandardStreamableHTTPServerTransport } from 'npm:@modelcontextprotocol/sdk@1.25.3/server/webStandardStreamableHttp.js'
|
|
27
|
+
import { Hono } from 'npm:hono@^4.9.7'
|
|
28
|
+
import { z } from 'npm:zod@^4.1.13'
|
|
29
|
+
|
|
30
|
+
// ─────────────────────────────────────────────────────────────
|
|
31
|
+
// DATA: Bounties
|
|
32
|
+
// ─────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
interface Bounty {
|
|
35
|
+
id: string
|
|
36
|
+
name: string
|
|
37
|
+
category: 'entry' | 'revenue' | 'merch' | 'referral' | 'engagement'
|
|
38
|
+
description: string
|
|
39
|
+
amount: number
|
|
40
|
+
currency: string
|
|
41
|
+
lock_period: string
|
|
42
|
+
lock_days: number
|
|
43
|
+
repeatable: boolean
|
|
44
|
+
requirements: string[]
|
|
45
|
+
tags: string[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const BOUNTIES: Bounty[] = [
|
|
49
|
+
// ── Entry Bounties ──
|
|
50
|
+
{
|
|
51
|
+
id: 'bounty-signup',
|
|
52
|
+
name: 'Sign-Up Bounty',
|
|
53
|
+
category: 'entry',
|
|
54
|
+
description: 'Create your NCTR account and complete onboarding. Earn 625 NCTR just for joining.',
|
|
55
|
+
amount: 625,
|
|
56
|
+
currency: 'NCTR',
|
|
57
|
+
lock_period: '360LOCK',
|
|
58
|
+
lock_days: 360,
|
|
59
|
+
repeatable: false,
|
|
60
|
+
requirements: ['Create account', 'Verify email', 'Complete profile'],
|
|
61
|
+
tags: ['new-member', 'onboarding', 'easy']
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'bounty-early-adopter',
|
|
65
|
+
name: 'Early Adopter Bonus',
|
|
66
|
+
category: 'entry',
|
|
67
|
+
description: 'Launch period only — earn 1,250 NCTR as an early adopter. Available during launch period.',
|
|
68
|
+
amount: 1250,
|
|
69
|
+
currency: 'NCTR',
|
|
70
|
+
lock_period: '360LOCK',
|
|
71
|
+
lock_days: 360,
|
|
72
|
+
repeatable: false,
|
|
73
|
+
requirements: ['Join during launch period'],
|
|
74
|
+
tags: ['limited-time', 'launch', 'bonus']
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'bounty-profile-complete',
|
|
78
|
+
name: 'Profile Completion Bounty',
|
|
79
|
+
category: 'entry',
|
|
80
|
+
description: 'Fill out your full profile including avatar, bio, and preferences. Earn 375 NCTR.',
|
|
81
|
+
amount: 375,
|
|
82
|
+
currency: 'NCTR',
|
|
83
|
+
lock_period: '360LOCK',
|
|
84
|
+
lock_days: 360,
|
|
85
|
+
repeatable: false,
|
|
86
|
+
requirements: ['Upload avatar', 'Write bio', 'Set preferences'],
|
|
87
|
+
tags: ['onboarding', 'profile', 'easy']
|
|
88
|
+
},
|
|
89
|
+
// ── Revenue Bounties ──
|
|
90
|
+
{
|
|
91
|
+
id: 'bounty-first-purchase',
|
|
92
|
+
name: 'First Purchase Bounty',
|
|
93
|
+
category: 'revenue',
|
|
94
|
+
description: 'Make your first purchase through The Garden and earn 2,500 NCTR.',
|
|
95
|
+
amount: 2500,
|
|
96
|
+
currency: 'NCTR',
|
|
97
|
+
lock_period: '360LOCK',
|
|
98
|
+
lock_days: 360,
|
|
99
|
+
repeatable: false,
|
|
100
|
+
requirements: ['Complete first purchase through The Garden'],
|
|
101
|
+
tags: ['shopping', 'first-time', 'the-garden']
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'bounty-shop-and-earn',
|
|
105
|
+
name: 'Shop & Earn',
|
|
106
|
+
category: 'revenue',
|
|
107
|
+
description: 'Earn 250 NCTR per qualifying purchase through The Garden. No cap — earn every time you shop.',
|
|
108
|
+
amount: 250,
|
|
109
|
+
currency: 'NCTR',
|
|
110
|
+
lock_period: '360LOCK',
|
|
111
|
+
lock_days: 360,
|
|
112
|
+
repeatable: true,
|
|
113
|
+
requirements: ['Make a qualifying purchase through The Garden'],
|
|
114
|
+
tags: ['shopping', 'recurring', 'the-garden', 'uncapped']
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'bounty-quarterly-spend',
|
|
118
|
+
name: 'Quarterly Spend Bounty',
|
|
119
|
+
category: 'revenue',
|
|
120
|
+
description: 'Hit the quarterly spend threshold and earn 5,000 NCTR. Resets each quarter.',
|
|
121
|
+
amount: 5000,
|
|
122
|
+
currency: 'NCTR',
|
|
123
|
+
lock_period: '360LOCK',
|
|
124
|
+
lock_days: 360,
|
|
125
|
+
repeatable: true,
|
|
126
|
+
requirements: ['Reach quarterly spend threshold'],
|
|
127
|
+
tags: ['shopping', 'quarterly', 'milestone']
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'bounty-annual-member',
|
|
131
|
+
name: 'Annual Member Bounty',
|
|
132
|
+
category: 'revenue',
|
|
133
|
+
description: 'Maintain active membership for a full year and earn 10,000 NCTR.',
|
|
134
|
+
amount: 10000,
|
|
135
|
+
currency: 'NCTR',
|
|
136
|
+
lock_period: '360LOCK',
|
|
137
|
+
lock_days: 360,
|
|
138
|
+
repeatable: true,
|
|
139
|
+
requirements: ['Maintain active membership for 12 months'],
|
|
140
|
+
tags: ['loyalty', 'annual', 'milestone']
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: 'bounty-whale-spend',
|
|
144
|
+
name: 'Whale Spend Bounty',
|
|
145
|
+
category: 'revenue',
|
|
146
|
+
description: 'Reach the top-tier annual spend level and earn 25,000 NCTR.',
|
|
147
|
+
amount: 25000,
|
|
148
|
+
currency: 'NCTR',
|
|
149
|
+
lock_period: '360LOCK',
|
|
150
|
+
lock_days: 360,
|
|
151
|
+
repeatable: true,
|
|
152
|
+
requirements: ['Reach top-tier annual spend threshold'],
|
|
153
|
+
tags: ['premium', 'annual', 'high-value']
|
|
154
|
+
},
|
|
155
|
+
// ── Merch Bounties ──
|
|
156
|
+
{
|
|
157
|
+
id: 'bounty-merch-collection',
|
|
158
|
+
name: 'Merch Collection Bounty',
|
|
159
|
+
category: 'merch',
|
|
160
|
+
description: 'Purchase from an NCTR merch collection and earn 5,000 NCTR.',
|
|
161
|
+
amount: 5000,
|
|
162
|
+
currency: 'NCTR',
|
|
163
|
+
lock_period: '360LOCK',
|
|
164
|
+
lock_days: 360,
|
|
165
|
+
repeatable: false,
|
|
166
|
+
requirements: ['Purchase from official NCTR merch collection'],
|
|
167
|
+
tags: ['merch', 'collection', 'branded']
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'bounty-merch-repeat',
|
|
171
|
+
name: 'Merch Repeat Bounty',
|
|
172
|
+
category: 'merch',
|
|
173
|
+
description: 'Earn 500 NCTR for each additional merch purchase.',
|
|
174
|
+
amount: 500,
|
|
175
|
+
currency: 'NCTR',
|
|
176
|
+
lock_period: '360LOCK',
|
|
177
|
+
lock_days: 360,
|
|
178
|
+
repeatable: true,
|
|
179
|
+
requirements: ['Purchase additional merch items'],
|
|
180
|
+
tags: ['merch', 'recurring']
|
|
181
|
+
},
|
|
182
|
+
// ── Referral Bounties ──
|
|
183
|
+
{
|
|
184
|
+
id: 'bounty-referral-signup',
|
|
185
|
+
name: 'Referral Sign-Up Bounty',
|
|
186
|
+
category: 'referral',
|
|
187
|
+
description: 'Refer a friend who creates an account. Earn 625 NCTR per referral.',
|
|
188
|
+
amount: 625,
|
|
189
|
+
currency: 'NCTR',
|
|
190
|
+
lock_period: '360LOCK',
|
|
191
|
+
lock_days: 360,
|
|
192
|
+
repeatable: true,
|
|
193
|
+
requirements: ['Referred friend creates account'],
|
|
194
|
+
tags: ['referral', 'friend', 'recurring']
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: 'bounty-referral-purchase',
|
|
198
|
+
name: 'Referral Purchase Bounty',
|
|
199
|
+
category: 'referral',
|
|
200
|
+
description: 'Earn 2,500 NCTR when your referral makes their first purchase.',
|
|
201
|
+
amount: 2500,
|
|
202
|
+
currency: 'NCTR',
|
|
203
|
+
lock_period: '360LOCK',
|
|
204
|
+
lock_days: 360,
|
|
205
|
+
repeatable: true,
|
|
206
|
+
requirements: ['Referred friend completes first purchase'],
|
|
207
|
+
tags: ['referral', 'purchase', 'recurring']
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: 'bounty-early-referral',
|
|
211
|
+
name: 'Early Referral Bonus',
|
|
212
|
+
category: 'referral',
|
|
213
|
+
description: 'Launch period referral bonus — earn 500 NCTR per referral during early access.',
|
|
214
|
+
amount: 500,
|
|
215
|
+
currency: 'NCTR',
|
|
216
|
+
lock_period: '360LOCK',
|
|
217
|
+
lock_days: 360,
|
|
218
|
+
repeatable: true,
|
|
219
|
+
requirements: ['Refer during launch period'],
|
|
220
|
+
tags: ['referral', 'launch', 'limited-time']
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'bounty-referral-milestone-5',
|
|
224
|
+
name: 'Referral Milestone — 5 Friends',
|
|
225
|
+
category: 'referral',
|
|
226
|
+
description: 'Refer 5 friends who all sign up. Earn a 2,500 NCTR milestone bonus.',
|
|
227
|
+
amount: 2500,
|
|
228
|
+
currency: 'NCTR',
|
|
229
|
+
lock_period: '360LOCK',
|
|
230
|
+
lock_days: 360,
|
|
231
|
+
repeatable: false,
|
|
232
|
+
requirements: ['5 referred friends create accounts'],
|
|
233
|
+
tags: ['referral', 'milestone']
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: 'bounty-referral-milestone-20',
|
|
237
|
+
name: 'Referral Milestone — 20 Friends',
|
|
238
|
+
category: 'referral',
|
|
239
|
+
description: 'Refer 20 friends who all sign up. Earn a 5,000 NCTR milestone bonus.',
|
|
240
|
+
amount: 5000,
|
|
241
|
+
currency: 'NCTR',
|
|
242
|
+
lock_period: '360LOCK',
|
|
243
|
+
lock_days: 360,
|
|
244
|
+
repeatable: false,
|
|
245
|
+
requirements: ['20 referred friends create accounts'],
|
|
246
|
+
tags: ['referral', 'milestone', 'ambassador']
|
|
247
|
+
},
|
|
248
|
+
// ── Engagement Bounties ──
|
|
249
|
+
{
|
|
250
|
+
id: 'bounty-social-share',
|
|
251
|
+
name: 'Social Share Bounty',
|
|
252
|
+
category: 'engagement',
|
|
253
|
+
description: 'Share NCTR on social media and earn 500 NCTR.',
|
|
254
|
+
amount: 500,
|
|
255
|
+
currency: 'NCTR',
|
|
256
|
+
lock_period: '360LOCK',
|
|
257
|
+
lock_days: 360,
|
|
258
|
+
repeatable: false,
|
|
259
|
+
requirements: ['Share NCTR content on social media', 'Tag @NCTRAlliance'],
|
|
260
|
+
tags: ['social', 'sharing', 'easy']
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: 'bounty-monthly-engagement',
|
|
264
|
+
name: 'Monthly Engagement Bounty',
|
|
265
|
+
category: 'engagement',
|
|
266
|
+
description: 'Stay active each month — earn 250 NCTR per month, up to 4 months.',
|
|
267
|
+
amount: 250,
|
|
268
|
+
currency: 'NCTR',
|
|
269
|
+
lock_period: '360LOCK',
|
|
270
|
+
lock_days: 360,
|
|
271
|
+
repeatable: true,
|
|
272
|
+
requirements: ['Log in and complete qualifying activity each month'],
|
|
273
|
+
tags: ['monthly', 'activity', 'recurring']
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: 'bounty-community-contributor',
|
|
277
|
+
name: 'Community Contributor Bounty',
|
|
278
|
+
category: 'engagement',
|
|
279
|
+
description: 'Make a meaningful contribution to the NCTR community and earn 2,000 NCTR.',
|
|
280
|
+
amount: 2000,
|
|
281
|
+
currency: 'NCTR',
|
|
282
|
+
lock_period: '360LOCK',
|
|
283
|
+
lock_days: 360,
|
|
284
|
+
repeatable: false,
|
|
285
|
+
requirements: ['Submit approved community contribution'],
|
|
286
|
+
tags: ['community', 'contribution']
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: 'bounty-impact-engine-join',
|
|
290
|
+
name: 'Impact Engine Join Bounty',
|
|
291
|
+
category: 'engagement',
|
|
292
|
+
description: 'Join an Impact Engine community and earn 5,000 NCTR.',
|
|
293
|
+
amount: 5000,
|
|
294
|
+
currency: 'NCTR',
|
|
295
|
+
lock_period: '360LOCK',
|
|
296
|
+
lock_days: 360,
|
|
297
|
+
repeatable: false,
|
|
298
|
+
requirements: ['Join any Impact Engine community'],
|
|
299
|
+
tags: ['impact-engine', 'community', 'joining']
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
|
|
303
|
+
// ─────────────────────────────────────────────────────────────
|
|
304
|
+
// DATA: Crescendo Status Tiers
|
|
305
|
+
// ─────────────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
interface Tier {
|
|
308
|
+
name: string
|
|
309
|
+
threshold: number
|
|
310
|
+
multiplier: number
|
|
311
|
+
description: string
|
|
312
|
+
perks: string[]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const TIERS: Tier[] = [
|
|
316
|
+
{
|
|
317
|
+
name: 'Bronze',
|
|
318
|
+
threshold: 0,
|
|
319
|
+
multiplier: 1.0,
|
|
320
|
+
description: 'Starting tier — all members begin here. Base earning rate on all bounties.',
|
|
321
|
+
perks: ['Access to all standard bounties', 'The Garden shopping', 'Community access']
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: 'Silver',
|
|
325
|
+
threshold: 1000,
|
|
326
|
+
multiplier: 1.2,
|
|
327
|
+
description: 'Committed members with 1,000+ NCTR locked. 1.2x earning multiplier.',
|
|
328
|
+
perks: ['1.2x earning multiplier', 'Early access to new bounties', 'Silver badge']
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: 'Gold',
|
|
332
|
+
threshold: 5000,
|
|
333
|
+
multiplier: 1.5,
|
|
334
|
+
description: 'Active members with 5,000+ NCTR locked. 1.5x earning multiplier.',
|
|
335
|
+
perks: ['1.5x earning multiplier', 'Exclusive Gold bounties', 'Priority support', 'Gold badge']
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: 'Platinum',
|
|
339
|
+
threshold: 25000,
|
|
340
|
+
multiplier: 1.8,
|
|
341
|
+
description: 'Power members with 25,000+ NCTR locked. 1.8x earning multiplier.',
|
|
342
|
+
perks: ['1.8x earning multiplier', 'VIP experiences', 'Platinum-only drops', 'Platinum badge']
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: 'Diamond',
|
|
346
|
+
threshold: 100000,
|
|
347
|
+
multiplier: 2.0,
|
|
348
|
+
description: 'Top-tier members with 100,000+ NCTR locked. 2.0x earning multiplier.',
|
|
349
|
+
perks: ['2.0x earning multiplier', 'Founding member recognition', 'Diamond-exclusive events', 'Diamond badge', 'Direct team access']
|
|
350
|
+
}
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
// ─────────────────────────────────────────────────────────────
|
|
354
|
+
// DATA: Impact Engines
|
|
355
|
+
// ─────────────────────────────────────────────────────────────
|
|
356
|
+
|
|
357
|
+
interface ImpactEngine {
|
|
358
|
+
name: string
|
|
359
|
+
slug: string
|
|
360
|
+
category: string
|
|
361
|
+
description: string
|
|
362
|
+
status: string
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const IMPACT_ENGINES: ImpactEngine[] = [
|
|
366
|
+
{
|
|
367
|
+
name: 'THROTTLE',
|
|
368
|
+
slug: 'throttle',
|
|
369
|
+
category: 'Powersports',
|
|
370
|
+
description: 'The powersports community within NCTR. Connecting riders, racers, and off-road enthusiasts through shared rewards.',
|
|
371
|
+
status: 'active'
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: 'GROUNDBALL',
|
|
375
|
+
slug: 'groundball',
|
|
376
|
+
category: 'Lacrosse',
|
|
377
|
+
description: 'The lacrosse community within NCTR. Uniting players, coaches, and fans at every level of the sport.',
|
|
378
|
+
status: 'active'
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: 'STARDUST',
|
|
382
|
+
slug: 'stardust',
|
|
383
|
+
category: 'Entertainment',
|
|
384
|
+
description: 'The entertainment community within NCTR. Connecting creators, performers, and fans across music, film, and live events.',
|
|
385
|
+
status: 'active'
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: 'SWEAT',
|
|
389
|
+
slug: 'sweat',
|
|
390
|
+
category: 'Skilled Trades',
|
|
391
|
+
description: 'The skilled trades community within NCTR. Supporting tradespeople, apprentices, and craftworkers.',
|
|
392
|
+
status: 'active'
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: 'SISU',
|
|
396
|
+
slug: 'sisu',
|
|
397
|
+
category: 'Recovery',
|
|
398
|
+
description: 'The recovery community within NCTR. A judgment-free space for people on recovery journeys.',
|
|
399
|
+
status: 'active'
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: 'INSPIRATION',
|
|
403
|
+
slug: 'inspiration',
|
|
404
|
+
category: 'Wellness',
|
|
405
|
+
description: 'The wellness community within NCTR. Focused on mindfulness, fitness, nutrition, and holistic well-being.',
|
|
406
|
+
status: 'active'
|
|
407
|
+
}
|
|
408
|
+
]
|
|
409
|
+
|
|
410
|
+
// ─────────────────────────────────────────────────────────────
|
|
411
|
+
// DATA: Active Promotions
|
|
412
|
+
// ─────────────────────────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
interface Promotion {
|
|
415
|
+
name: string
|
|
416
|
+
description: string
|
|
417
|
+
type: string
|
|
418
|
+
bounty_id: string | null
|
|
419
|
+
active: boolean
|
|
420
|
+
details: string
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const PROMOTIONS: Promotion[] = [
|
|
424
|
+
{
|
|
425
|
+
name: 'Early Adopter Bonus',
|
|
426
|
+
description: 'Earn 1,250 NCTR just for joining during the launch period. No counter, no slot limit — available while the launch window is open.',
|
|
427
|
+
type: 'launch-bonus',
|
|
428
|
+
bounty_id: 'bounty-early-adopter',
|
|
429
|
+
active: true,
|
|
430
|
+
details: 'Launch period only. 1,250 NCTR with 360LOCK. Open to all new members.'
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: 'Early Referral Bonus',
|
|
434
|
+
description: 'During the launch period, earn 500 NCTR for each friend you refer (instead of standard 100 NCTR post-launch).',
|
|
435
|
+
type: 'referral-boost',
|
|
436
|
+
bounty_id: 'bounty-early-referral',
|
|
437
|
+
active: true,
|
|
438
|
+
details: 'Launch period only. 5x the standard referral rate. No cap on referrals.'
|
|
439
|
+
}
|
|
440
|
+
]
|
|
441
|
+
|
|
442
|
+
// ─────────────────────────────────────────────────────────────
|
|
443
|
+
// MCP SERVER
|
|
444
|
+
// ─────────────────────────────────────────────────────────────
|
|
445
|
+
|
|
446
|
+
const app = new Hono()
|
|
447
|
+
|
|
448
|
+
const server = new McpServer({
|
|
449
|
+
name: 'nctr-alliance',
|
|
450
|
+
version: '1.0.0',
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
// ── Tool 1: search_bounties ──
|
|
454
|
+
|
|
455
|
+
server.registerTool(
|
|
456
|
+
'search_bounties',
|
|
457
|
+
{
|
|
458
|
+
title: 'Search Bounties',
|
|
459
|
+
description:
|
|
460
|
+
'Search and filter available NCTR bounties. Filter by category (entry, revenue, merch, referral, engagement), minimum/maximum NCTR amount, or keyword. Returns bounty details including name, amount, lock period, and requirements. All bounties use 360LOCK — tokens stay yours after the lock period.',
|
|
461
|
+
inputSchema: {
|
|
462
|
+
category: z
|
|
463
|
+
.enum(['entry', 'revenue', 'merch', 'referral', 'engagement'])
|
|
464
|
+
.optional()
|
|
465
|
+
.describe('Filter by bounty category'),
|
|
466
|
+
min_amount: z.number().optional().describe('Minimum NCTR amount'),
|
|
467
|
+
max_amount: z.number().optional().describe('Maximum NCTR amount'),
|
|
468
|
+
keyword: z
|
|
469
|
+
.string()
|
|
470
|
+
.optional()
|
|
471
|
+
.describe('Search keyword to match against bounty name, description, or tags'),
|
|
472
|
+
repeatable_only: z
|
|
473
|
+
.boolean()
|
|
474
|
+
.optional()
|
|
475
|
+
.describe('If true, only return bounties that can be earned multiple times'),
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
({ category, min_amount, max_amount, keyword, repeatable_only }) => {
|
|
479
|
+
let results = [...BOUNTIES]
|
|
480
|
+
|
|
481
|
+
if (category) {
|
|
482
|
+
results = results.filter((b) => b.category === category)
|
|
483
|
+
}
|
|
484
|
+
if (min_amount !== undefined) {
|
|
485
|
+
results = results.filter((b) => b.amount >= min_amount)
|
|
486
|
+
}
|
|
487
|
+
if (max_amount !== undefined) {
|
|
488
|
+
results = results.filter((b) => b.amount <= max_amount)
|
|
489
|
+
}
|
|
490
|
+
if (keyword) {
|
|
491
|
+
const kw = keyword.toLowerCase()
|
|
492
|
+
results = results.filter(
|
|
493
|
+
(b) =>
|
|
494
|
+
b.name.toLowerCase().includes(kw) ||
|
|
495
|
+
b.description.toLowerCase().includes(kw) ||
|
|
496
|
+
b.tags.some((t) => t.includes(kw))
|
|
497
|
+
)
|
|
498
|
+
}
|
|
499
|
+
if (repeatable_only) {
|
|
500
|
+
results = results.filter((b) => b.repeatable)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (results.length === 0) {
|
|
504
|
+
return {
|
|
505
|
+
content: [
|
|
506
|
+
{
|
|
507
|
+
type: 'text',
|
|
508
|
+
text: 'No bounties found matching your filters. Try broadening your search — there are 19 bounties across 5 categories.',
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const formatted = results.map((b) => ({
|
|
515
|
+
id: b.id,
|
|
516
|
+
name: b.name,
|
|
517
|
+
category: b.category,
|
|
518
|
+
amount: `${b.amount.toLocaleString()} NCTR`,
|
|
519
|
+
lock: b.lock_period,
|
|
520
|
+
repeatable: b.repeatable,
|
|
521
|
+
description: b.description,
|
|
522
|
+
requirements: b.requirements,
|
|
523
|
+
}))
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
content: [
|
|
527
|
+
{
|
|
528
|
+
type: 'text',
|
|
529
|
+
text: JSON.stringify(
|
|
530
|
+
{
|
|
531
|
+
total: results.length,
|
|
532
|
+
bounties: formatted,
|
|
533
|
+
note: 'All bounties use 360LOCK (exactly 360 days). Tokens are committed, not spent — they stay yours after the lock period.',
|
|
534
|
+
},
|
|
535
|
+
null,
|
|
536
|
+
2
|
|
537
|
+
),
|
|
538
|
+
},
|
|
539
|
+
],
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
// ── Tool 2: get_earning_rates ──
|
|
545
|
+
|
|
546
|
+
server.registerTool(
|
|
547
|
+
'get_earning_rates',
|
|
548
|
+
{
|
|
549
|
+
title: 'Get Earning Rates',
|
|
550
|
+
description:
|
|
551
|
+
'Get NCTR earning rates for a specific Crescendo status tier, or compare all tiers. Shows the multiplier applied to bounty earnings at each tier level. Higher tiers earn more NCTR per bounty.',
|
|
552
|
+
inputSchema: {
|
|
553
|
+
tier: z
|
|
554
|
+
.enum(['Bronze', 'Silver', 'Gold', 'Platinum', 'Diamond'])
|
|
555
|
+
.optional()
|
|
556
|
+
.describe('Specific tier to check. Omit to see all tiers.'),
|
|
557
|
+
bounty_id: z
|
|
558
|
+
.string()
|
|
559
|
+
.optional()
|
|
560
|
+
.describe('Optional bounty ID to calculate tier-adjusted earning amount'),
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
({ tier, bounty_id }) => {
|
|
564
|
+
let tiers = [...TIERS]
|
|
565
|
+
if (tier) {
|
|
566
|
+
tiers = tiers.filter((t) => t.name === tier)
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const bounty = bounty_id ? BOUNTIES.find((b) => b.id === bounty_id) : null
|
|
570
|
+
|
|
571
|
+
const formatted = tiers.map((t) => {
|
|
572
|
+
const result: Record<string, unknown> = {
|
|
573
|
+
tier: t.name,
|
|
574
|
+
nctr_threshold: `${t.threshold.toLocaleString()} NCTR`,
|
|
575
|
+
multiplier: `${t.multiplier}x`,
|
|
576
|
+
description: t.description,
|
|
577
|
+
perks: t.perks,
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (bounty) {
|
|
581
|
+
const adjusted = Math.floor(bounty.amount * t.multiplier)
|
|
582
|
+
result.bounty_name = bounty.name
|
|
583
|
+
result.base_amount = `${bounty.amount.toLocaleString()} NCTR`
|
|
584
|
+
result.adjusted_amount = `${adjusted.toLocaleString()} NCTR`
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return result
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
return {
|
|
591
|
+
content: [
|
|
592
|
+
{
|
|
593
|
+
type: 'text',
|
|
594
|
+
text: JSON.stringify(
|
|
595
|
+
{
|
|
596
|
+
tiers: formatted,
|
|
597
|
+
note: 'Crescendo Status Tiers determine your earning multiplier. Lock more NCTR to progress through tiers and earn more on every bounty.',
|
|
598
|
+
},
|
|
599
|
+
null,
|
|
600
|
+
2
|
|
601
|
+
),
|
|
602
|
+
},
|
|
603
|
+
],
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
// ── Tool 3: check_tier_requirements ──
|
|
609
|
+
|
|
610
|
+
server.registerTool(
|
|
611
|
+
'check_tier_requirements',
|
|
612
|
+
{
|
|
613
|
+
title: 'Check Tier Requirements',
|
|
614
|
+
description:
|
|
615
|
+
'Check what is needed to reach a specific Crescendo status tier, or see the full progression path. Shows NCTR lock thresholds, multipliers, and perks for each tier.',
|
|
616
|
+
inputSchema: {
|
|
617
|
+
current_balance: z
|
|
618
|
+
.number()
|
|
619
|
+
.optional()
|
|
620
|
+
.describe('Current NCTR balance to check tier status and progress to next tier'),
|
|
621
|
+
target_tier: z
|
|
622
|
+
.enum(['Bronze', 'Silver', 'Gold', 'Platinum', 'Diamond'])
|
|
623
|
+
.optional()
|
|
624
|
+
.describe('Target tier to check requirements for'),
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
({ current_balance, target_tier }) => {
|
|
628
|
+
if (target_tier) {
|
|
629
|
+
const tier = TIERS.find((t) => t.name === target_tier)!
|
|
630
|
+
const result: Record<string, unknown> = {
|
|
631
|
+
tier: tier.name,
|
|
632
|
+
nctr_required: `${tier.threshold.toLocaleString()} NCTR`,
|
|
633
|
+
multiplier: `${tier.multiplier}x`,
|
|
634
|
+
perks: tier.perks,
|
|
635
|
+
description: tier.description,
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (current_balance !== undefined) {
|
|
639
|
+
if (current_balance >= tier.threshold) {
|
|
640
|
+
result.status = 'QUALIFIED'
|
|
641
|
+
result.message = `You already qualify for ${tier.name} with ${current_balance.toLocaleString()} NCTR.`
|
|
642
|
+
} else {
|
|
643
|
+
const needed = tier.threshold - current_balance
|
|
644
|
+
result.status = 'NOT YET'
|
|
645
|
+
result.nctr_needed = `${needed.toLocaleString()} NCTR`
|
|
646
|
+
result.message = `You need ${needed.toLocaleString()} more NCTR to reach ${tier.name}.`
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Full tier progression
|
|
656
|
+
const progression = TIERS.map((t) => {
|
|
657
|
+
const result: Record<string, unknown> = {
|
|
658
|
+
tier: t.name,
|
|
659
|
+
nctr_required: `${t.threshold.toLocaleString()} NCTR`,
|
|
660
|
+
multiplier: `${t.multiplier}x`,
|
|
661
|
+
perks: t.perks,
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (current_balance !== undefined) {
|
|
665
|
+
result.qualified = current_balance >= t.threshold
|
|
666
|
+
if (current_balance < t.threshold) {
|
|
667
|
+
result.nctr_needed = `${(t.threshold - current_balance).toLocaleString()} NCTR`
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return result
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
let current_tier = 'Bronze'
|
|
675
|
+
if (current_balance !== undefined) {
|
|
676
|
+
for (const t of [...TIERS].reverse()) {
|
|
677
|
+
if (current_balance >= t.threshold) {
|
|
678
|
+
current_tier = t.name
|
|
679
|
+
break
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return {
|
|
685
|
+
content: [
|
|
686
|
+
{
|
|
687
|
+
type: 'text',
|
|
688
|
+
text: JSON.stringify(
|
|
689
|
+
{
|
|
690
|
+
current_tier: current_balance !== undefined ? current_tier : undefined,
|
|
691
|
+
current_balance:
|
|
692
|
+
current_balance !== undefined
|
|
693
|
+
? `${current_balance.toLocaleString()} NCTR`
|
|
694
|
+
: undefined,
|
|
695
|
+
progression,
|
|
696
|
+
note: 'Tiers are based on total NCTR locked. Higher tiers unlock better multipliers and exclusive perks.',
|
|
697
|
+
},
|
|
698
|
+
null,
|
|
699
|
+
2
|
|
700
|
+
),
|
|
701
|
+
},
|
|
702
|
+
],
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
// ── Tool 4: get_active_promotions ──
|
|
708
|
+
|
|
709
|
+
server.registerTool(
|
|
710
|
+
'get_active_promotions',
|
|
711
|
+
{
|
|
712
|
+
title: 'Get Active Promotions',
|
|
713
|
+
description:
|
|
714
|
+
'Get currently active promotions, limited-time offers, and special earning opportunities in the NCTR program.',
|
|
715
|
+
inputSchema: {},
|
|
716
|
+
},
|
|
717
|
+
() => {
|
|
718
|
+
const active = PROMOTIONS.filter((p) => p.active)
|
|
719
|
+
|
|
720
|
+
if (active.length === 0) {
|
|
721
|
+
return {
|
|
722
|
+
content: [
|
|
723
|
+
{
|
|
724
|
+
type: 'text',
|
|
725
|
+
text: 'No special promotions are active right now. Check back soon — new opportunities launch regularly.',
|
|
726
|
+
},
|
|
727
|
+
],
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const formatted = active.map((p) => ({
|
|
732
|
+
name: p.name,
|
|
733
|
+
type: p.type,
|
|
734
|
+
description: p.description,
|
|
735
|
+
details: p.details,
|
|
736
|
+
related_bounty: p.bounty_id,
|
|
737
|
+
}))
|
|
738
|
+
|
|
739
|
+
return {
|
|
740
|
+
content: [
|
|
741
|
+
{
|
|
742
|
+
type: 'text',
|
|
743
|
+
text: JSON.stringify(
|
|
744
|
+
{
|
|
745
|
+
active_promotions: formatted,
|
|
746
|
+
count: active.length,
|
|
747
|
+
note: 'Promotions are time-limited. Join now to take advantage of launch period bonuses.',
|
|
748
|
+
},
|
|
749
|
+
null,
|
|
750
|
+
2
|
|
751
|
+
),
|
|
752
|
+
},
|
|
753
|
+
],
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
// ── Tool 5: get_onboarding_link ──
|
|
759
|
+
|
|
760
|
+
server.registerTool(
|
|
761
|
+
'get_onboarding_link',
|
|
762
|
+
{
|
|
763
|
+
title: 'Get Onboarding Link',
|
|
764
|
+
description:
|
|
765
|
+
'Generate a link for a new member to join NCTR Alliance. Optionally include a referral code. Returns the sign-up URL and a summary of what new members earn.',
|
|
766
|
+
inputSchema: {
|
|
767
|
+
referral_code: z
|
|
768
|
+
.string()
|
|
769
|
+
.optional()
|
|
770
|
+
.describe('Optional referral code to credit the referring member'),
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
({ referral_code }) => {
|
|
774
|
+
const baseUrl = 'https://crescendo.nctr.live/join'
|
|
775
|
+
const url = referral_code ? `${baseUrl}?ref=${encodeURIComponent(referral_code)}` : baseUrl
|
|
776
|
+
|
|
777
|
+
const welcomePackage = {
|
|
778
|
+
url,
|
|
779
|
+
welcome_bounties: [
|
|
780
|
+
{ name: 'Sign-Up Bounty', amount: '625 NCTR', lock: '360LOCK' },
|
|
781
|
+
{ name: 'Early Adopter Bonus', amount: '1,250 NCTR', lock: '360LOCK', note: 'Launch period only' },
|
|
782
|
+
{ name: 'Profile Completion Bounty', amount: '375 NCTR', lock: '360LOCK' },
|
|
783
|
+
],
|
|
784
|
+
total_potential: '2,250 NCTR',
|
|
785
|
+
referral_code: referral_code || null,
|
|
786
|
+
note: 'New members can earn up to 2,250 NCTR just by signing up and completing their profile during the launch period. All tokens use 360LOCK — committed, not spent.',
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return {
|
|
790
|
+
content: [{ type: 'text', text: JSON.stringify(welcomePackage, null, 2) }],
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
// ── Tool 6: get_impact_engines ──
|
|
796
|
+
|
|
797
|
+
server.registerTool(
|
|
798
|
+
'get_impact_engines',
|
|
799
|
+
{
|
|
800
|
+
title: 'Get Impact Engines',
|
|
801
|
+
description:
|
|
802
|
+
'Discover NCTR Impact Engine communities. Impact Engines are passion-based communities within NCTR — each focused on a different lifestyle vertical. Members can join any Impact Engine to connect with like-minded people and access community-specific experiences.',
|
|
803
|
+
inputSchema: {
|
|
804
|
+
category: z
|
|
805
|
+
.string()
|
|
806
|
+
.optional()
|
|
807
|
+
.describe(
|
|
808
|
+
'Filter by category (Powersports, Lacrosse, Entertainment, Skilled Trades, Recovery, Wellness)'
|
|
809
|
+
),
|
|
810
|
+
},
|
|
811
|
+
},
|
|
812
|
+
({ category }) => {
|
|
813
|
+
let engines = [...IMPACT_ENGINES]
|
|
814
|
+
|
|
815
|
+
if (category) {
|
|
816
|
+
const cat = category.toLowerCase()
|
|
817
|
+
engines = engines.filter((e) => e.category.toLowerCase().includes(cat))
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (engines.length === 0) {
|
|
821
|
+
return {
|
|
822
|
+
content: [
|
|
823
|
+
{
|
|
824
|
+
type: 'text',
|
|
825
|
+
text: `No Impact Engines found for "${category}". Available categories: Powersports, Lacrosse, Entertainment, Skilled Trades, Recovery, Wellness.`,
|
|
826
|
+
},
|
|
827
|
+
],
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const formatted = engines.map((e) => ({
|
|
832
|
+
name: e.name,
|
|
833
|
+
category: e.category,
|
|
834
|
+
description: e.description,
|
|
835
|
+
status: e.status,
|
|
836
|
+
}))
|
|
837
|
+
|
|
838
|
+
return {
|
|
839
|
+
content: [
|
|
840
|
+
{
|
|
841
|
+
type: 'text',
|
|
842
|
+
text: JSON.stringify(
|
|
843
|
+
{
|
|
844
|
+
impact_engines: formatted,
|
|
845
|
+
count: engines.length,
|
|
846
|
+
bounty: 'Join any Impact Engine to earn the Impact Engine Join Bounty — 5,000 NCTR with 360LOCK.',
|
|
847
|
+
note: 'Impact Engines are passion-based communities. Each one connects people around a shared interest, with community-specific experiences and rewards.',
|
|
848
|
+
},
|
|
849
|
+
null,
|
|
850
|
+
2
|
|
851
|
+
),
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
// ─────────────────────────────────────────────────────────────
|
|
859
|
+
// HTTP HANDLER
|
|
860
|
+
// ─────────────────────────────────────────────────────────────
|
|
861
|
+
|
|
862
|
+
app.get('/', (c) => {
|
|
863
|
+
return c.json({
|
|
864
|
+
name: 'NCTR Alliance MCP Server',
|
|
865
|
+
version: '1.0.0',
|
|
866
|
+
description:
|
|
867
|
+
'Model Context Protocol server for AI agents to discover and interact with the NCTR rewards program.',
|
|
868
|
+
tools: [
|
|
869
|
+
'search_bounties',
|
|
870
|
+
'get_earning_rates',
|
|
871
|
+
'check_tier_requirements',
|
|
872
|
+
'get_active_promotions',
|
|
873
|
+
'get_onboarding_link',
|
|
874
|
+
'get_impact_engines',
|
|
875
|
+
],
|
|
876
|
+
links: {
|
|
877
|
+
website: 'https://nctr.live',
|
|
878
|
+
the_garden: 'https://thegarden.nctr.live',
|
|
879
|
+
onboarding: 'https://crescendo.nctr.live',
|
|
880
|
+
for_agents: 'https://thegarden.nctr.live/for-agents',
|
|
881
|
+
manifest: 'https://nctr.live/.well-known/nctr.json',
|
|
882
|
+
},
|
|
883
|
+
})
|
|
884
|
+
})
|
|
885
|
+
|
|
886
|
+
app.all('/mcp', async (c) => {
|
|
887
|
+
const transport = new WebStandardStreamableHTTPServerTransport()
|
|
888
|
+
await server.connect(transport)
|
|
889
|
+
return transport.handleRequest(c.req.raw)
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
// Health check
|
|
893
|
+
app.get('/health', (c) => {
|
|
894
|
+
return c.json({ status: 'ok', timestamp: new Date().toISOString() })
|
|
895
|
+
})
|
|
896
|
+
|
|
897
|
+
Deno.serve(app.fetch)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════════
|
|
3
|
+
# NCTR Alliance MCP Server — Test Script
|
|
4
|
+
# ═══════════════════════════════════════════════════════════════
|
|
5
|
+
# Usage:
|
|
6
|
+
# bash test-mcp-server.sh # Test local server
|
|
7
|
+
# bash test-mcp-server.sh https://xyz.supabase.co/functions/v1/mcp # Test production
|
|
8
|
+
# ═══════════════════════════════════════════════════════════════
|
|
9
|
+
|
|
10
|
+
BASE_URL="${1:-https://yhwcaodofmbusjurawhp.supabase.co/functions/v1/mcp}"
|
|
11
|
+
MCP_URL="${BASE_URL}/rpc"
|
|
12
|
+
PASS=0
|
|
13
|
+
FAIL=0
|
|
14
|
+
|
|
15
|
+
echo ""
|
|
16
|
+
echo "╔══════════════════════════════════════════════════════╗"
|
|
17
|
+
echo "║ NCTR Alliance MCP Server — Test Suite ║"
|
|
18
|
+
echo "╠══════════════════════════════════════════════════════╣"
|
|
19
|
+
echo "║ Target: $BASE_URL"
|
|
20
|
+
echo "╚══════════════════════════════════════════════════════╝"
|
|
21
|
+
echo ""
|
|
22
|
+
|
|
23
|
+
# ── Helper ──
|
|
24
|
+
run_test() {
|
|
25
|
+
local test_name="$1"
|
|
26
|
+
local method_name="$2"
|
|
27
|
+
local params="$3"
|
|
28
|
+
local expect_text="$4"
|
|
29
|
+
|
|
30
|
+
echo "─── Test: $test_name ───"
|
|
31
|
+
|
|
32
|
+
RESPONSE=$(curl -s -X POST "$MCP_URL" \
|
|
33
|
+
-H "Content-Type: application/json" \
|
|
34
|
+
-H "Accept: application/json, text/event-stream" \
|
|
35
|
+
-d "{
|
|
36
|
+
\"jsonrpc\": \"2.0\",
|
|
37
|
+
\"id\": 1,
|
|
38
|
+
\"method\": \"tools/call\",
|
|
39
|
+
\"params\": {
|
|
40
|
+
\"name\": \"$method_name\",
|
|
41
|
+
\"arguments\": $params
|
|
42
|
+
}
|
|
43
|
+
}")
|
|
44
|
+
|
|
45
|
+
# Extract data line from SSE response
|
|
46
|
+
DATA_LINE=$(echo "$RESPONSE" | grep "^data:" | head -1 | sed 's/^data: //')
|
|
47
|
+
|
|
48
|
+
if [ -z "$DATA_LINE" ]; then
|
|
49
|
+
# Try parsing as direct JSON (non-SSE response)
|
|
50
|
+
DATA_LINE="$RESPONSE"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
if echo "$DATA_LINE" | grep -q "$expect_text"; then
|
|
54
|
+
echo " ✅ PASS — Found expected: $expect_text"
|
|
55
|
+
PASS=$((PASS + 1))
|
|
56
|
+
else
|
|
57
|
+
echo " ❌ FAIL — Expected to find: $expect_text"
|
|
58
|
+
echo " Response: $(echo "$DATA_LINE" | head -c 200)"
|
|
59
|
+
FAIL=$((FAIL + 1))
|
|
60
|
+
fi
|
|
61
|
+
echo ""
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# ── Test 0: Health Check ──
|
|
65
|
+
echo "─── Test: Health Check ───"
|
|
66
|
+
HEALTH=$(curl -s "$BASE_URL/health")
|
|
67
|
+
if echo "$HEALTH" | grep -q '"status":"ok"'; then
|
|
68
|
+
echo " ✅ PASS — Server is healthy"
|
|
69
|
+
PASS=$((PASS + 1))
|
|
70
|
+
else
|
|
71
|
+
echo " ❌ FAIL — Health check failed"
|
|
72
|
+
echo " Response: $HEALTH"
|
|
73
|
+
FAIL=$((FAIL + 1))
|
|
74
|
+
fi
|
|
75
|
+
echo ""
|
|
76
|
+
|
|
77
|
+
# ── Test 1: Root Info ──
|
|
78
|
+
echo "─── Test: Root Info Endpoint ───"
|
|
79
|
+
ROOT=$(curl -s "$BASE_URL/")
|
|
80
|
+
if echo "$ROOT" | grep -q "NCTR Alliance MCP Server"; then
|
|
81
|
+
echo " ✅ PASS — Root endpoint returns server info"
|
|
82
|
+
PASS=$((PASS + 1))
|
|
83
|
+
else
|
|
84
|
+
echo " ❌ FAIL — Root endpoint missing server info"
|
|
85
|
+
FAIL=$((FAIL + 1))
|
|
86
|
+
fi
|
|
87
|
+
echo ""
|
|
88
|
+
|
|
89
|
+
# ── Test 2: search_bounties — All Bounties ──
|
|
90
|
+
run_test "search_bounties — All Bounties" \
|
|
91
|
+
"search_bounties" \
|
|
92
|
+
"{}" \
|
|
93
|
+
"total"
|
|
94
|
+
|
|
95
|
+
# ── Test 3: search_bounties — Filter by Category ──
|
|
96
|
+
run_test "search_bounties — Entry Category" \
|
|
97
|
+
"search_bounties" \
|
|
98
|
+
'{"category": "entry"}' \
|
|
99
|
+
"Sign-Up Bounty"
|
|
100
|
+
|
|
101
|
+
# ── Test 4: search_bounties — Filter by Amount ──
|
|
102
|
+
run_test "search_bounties — Min 5000 NCTR" \
|
|
103
|
+
"search_bounties" \
|
|
104
|
+
'{"min_amount": 5000}' \
|
|
105
|
+
"5,000 NCTR"
|
|
106
|
+
|
|
107
|
+
# ── Test 5: search_bounties — Keyword Search ──
|
|
108
|
+
run_test "search_bounties — Keyword: referral" \
|
|
109
|
+
"search_bounties" \
|
|
110
|
+
'{"keyword": "referral"}' \
|
|
111
|
+
"Referral"
|
|
112
|
+
|
|
113
|
+
# ── Test 6: search_bounties — Repeatable Only ──
|
|
114
|
+
run_test "search_bounties — Repeatable Only" \
|
|
115
|
+
"search_bounties" \
|
|
116
|
+
'{"repeatable_only": true}' \
|
|
117
|
+
"repeatable"
|
|
118
|
+
|
|
119
|
+
# ── Test 7: get_earning_rates — All Tiers ──
|
|
120
|
+
run_test "get_earning_rates — All Tiers" \
|
|
121
|
+
"get_earning_rates" \
|
|
122
|
+
"{}" \
|
|
123
|
+
"Diamond"
|
|
124
|
+
|
|
125
|
+
# ── Test 8: get_earning_rates — Specific Tier ──
|
|
126
|
+
run_test "get_earning_rates — Gold Tier" \
|
|
127
|
+
"get_earning_rates" \
|
|
128
|
+
'{"tier": "Gold"}' \
|
|
129
|
+
"1.5x"
|
|
130
|
+
|
|
131
|
+
# ── Test 9: get_earning_rates — With Bounty Calculation ──
|
|
132
|
+
run_test "get_earning_rates — Gold + First Purchase Bounty" \
|
|
133
|
+
"get_earning_rates" \
|
|
134
|
+
'{"tier": "Gold", "bounty_id": "bounty-first-purchase"}' \
|
|
135
|
+
"adjusted_amount"
|
|
136
|
+
|
|
137
|
+
# ── Test 10: check_tier_requirements — Full Progression ──
|
|
138
|
+
run_test "check_tier_requirements — Full Progression" \
|
|
139
|
+
"check_tier_requirements" \
|
|
140
|
+
"{}" \
|
|
141
|
+
"progression"
|
|
142
|
+
|
|
143
|
+
# ── Test 11: check_tier_requirements — With Balance ──
|
|
144
|
+
run_test "check_tier_requirements — Balance 3000 NCTR" \
|
|
145
|
+
"check_tier_requirements" \
|
|
146
|
+
'{"current_balance": 3000}' \
|
|
147
|
+
"Silver"
|
|
148
|
+
|
|
149
|
+
# ── Test 12: check_tier_requirements — Target Tier ──
|
|
150
|
+
run_test "check_tier_requirements — Target Diamond" \
|
|
151
|
+
"check_tier_requirements" \
|
|
152
|
+
'{"target_tier": "Diamond", "current_balance": 500}' \
|
|
153
|
+
"NOT YET"
|
|
154
|
+
|
|
155
|
+
# ── Test 13: get_active_promotions ──
|
|
156
|
+
run_test "get_active_promotions" \
|
|
157
|
+
"get_active_promotions" \
|
|
158
|
+
"{}" \
|
|
159
|
+
"Early Adopter"
|
|
160
|
+
|
|
161
|
+
# ── Test 14: get_onboarding_link — No Referral ──
|
|
162
|
+
run_test "get_onboarding_link — No Referral" \
|
|
163
|
+
"get_onboarding_link" \
|
|
164
|
+
"{}" \
|
|
165
|
+
"crescendo.nctr.live/join"
|
|
166
|
+
|
|
167
|
+
# ── Test 15: get_onboarding_link — With Referral ──
|
|
168
|
+
run_test "get_onboarding_link — With Referral Code" \
|
|
169
|
+
"get_onboarding_link" \
|
|
170
|
+
'{"referral_code": "ANDERSON123"}' \
|
|
171
|
+
"ANDERSON123"
|
|
172
|
+
|
|
173
|
+
# ── Test 16: get_impact_engines — All Engines ──
|
|
174
|
+
run_test "get_impact_engines — All Engines" \
|
|
175
|
+
"get_impact_engines" \
|
|
176
|
+
"{}" \
|
|
177
|
+
"THROTTLE"
|
|
178
|
+
|
|
179
|
+
# ── Test 17: get_impact_engines — Filter by Category ──
|
|
180
|
+
run_test "get_impact_engines — Lacrosse" \
|
|
181
|
+
"get_impact_engines" \
|
|
182
|
+
'{"category": "Lacrosse"}' \
|
|
183
|
+
"GROUNDBALL"
|
|
184
|
+
|
|
185
|
+
# ── Results ──
|
|
186
|
+
echo "╔══════════════════════════════════════════════════════╗"
|
|
187
|
+
echo "║ Results: $PASS passed, $FAIL failed "
|
|
188
|
+
echo "╚══════════════════════════════════════════════════════╝"
|
|
189
|
+
echo ""
|
|
190
|
+
|
|
191
|
+
if [ $FAIL -gt 0 ]; then
|
|
192
|
+
echo "⚠️ Some tests failed. Check the output above."
|
|
193
|
+
exit 1
|
|
194
|
+
else
|
|
195
|
+
echo "All tests passed."
|
|
196
|
+
exit 0
|
|
197
|
+
fi
|