@scorehub/mcp-server 0.1.4 → 0.2.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 (3) hide show
  1. package/README.md +267 -0
  2. package/dist/index.js +53 -29
  3. package/package.json +6 -2
package/README.md ADDED
@@ -0,0 +1,267 @@
1
+ # @scorehub/mcp-server
2
+
3
+ > Local MCP proxy for ScoreHub — handles OAuth login, token caching, and tool forwarding to the remote ScoreHub MCP endpoint.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@scorehub/mcp-server)](https://www.npmjs.com/package/@scorehub/mcp-server)
6
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
7
+ [![node: >=18](https://img.shields.io/badge/node-%3E%3D18-green.svg)](https://nodejs.org/)
8
+
9
+ ---
10
+
11
+ ## Overview
12
+
13
+ `@scorehub/mcp-server` is a **local MCP (Model Context Protocol) proxy** that sits between your AI agent (Claude Code, Claude Desktop, WorkBuddy, etc.) and the ScoreHub remote MCP endpoint. It handles:
14
+
15
+ - **OAuth authentication** — opens your browser for one-time ScoreHub login, then caches tokens locally
16
+ - **Token lifecycle** — automatic refresh 5 minutes before expiry, no repeated logins
17
+ - **Tool forwarding** — proxies TikTok Shop creator outreach tools to the remote API over Streamable HTTP
18
+
19
+ ```
20
+ ┌─────────────┐ stdio ┌──────────────────┐ Streamable HTTP ┌─────────────────┐
21
+ │ AI Agent │ ◄────────────► │ @scorehub/ │ ◄─────────────────► │ ScoreHub │
22
+ │ (Claude) │ │ mcp-server │ │ Remote MCP │
23
+ └─────────────┘ └──────────────────┘ └────────┬────────┘
24
+ │
25
+ TikTok Shop
26
+ Partner API
27
+ ```
28
+
29
+ ## Installation
30
+
31
+ **Prerequisites**: Node.js >= 18
32
+
33
+ ```bash
34
+ npm install -g @scorehub/mcp-server
35
+ ```
36
+
37
+ This installs the `scorehub-mcp` command globally. You can also skip global install and use `npx` directly in your MCP client configuration (see [Quick Start](#quick-start)).
38
+
39
+ ## Quick Start
40
+
41
+ Add this to your MCP client configuration file:
42
+
43
+ ### Claude Code / Claude Desktop
44
+
45
+ In `claude_desktop_config.json` or `.claude/mcp.json`:
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "scorehub": {
51
+ "command": "npx",
52
+ "args": ["-y", "@scorehub/mcp-server"],
53
+ "env": {
54
+ "SCOREHUB_REMOTE_MCP_URL": "https://mcp.scorehub.cn"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ### WorkBuddy
62
+
63
+ In `mcp.json`:
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "scorehub": {
69
+ "command": "npx",
70
+ "args": ["-y", "@scorehub/mcp-server"],
71
+ "env": {
72
+ "SCOREHUB_REMOTE_MCP_URL": "https://mcp.scorehub.cn"
73
+ }
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### What happens on first run
80
+
81
+ 1. The server starts and connects to the remote MCP endpoint
82
+ 2. It fetches the list of available tools
83
+ 3. The first time you use a remote tool, you'll be prompted to run the `authorize` tool
84
+ 4. Your browser opens to the ScoreHub login page
85
+ 5. After authorizing, the browser redirects to `localhost:19836` and the token is saved
86
+ 6. Subsequent calls use the cached token — no repeated login needed
87
+
88
+ ## Configuration
89
+
90
+ ### Environment variables
91
+
92
+ | Variable | Required | Default | Description |
93
+ |---|---|---|---|
94
+ | `SCOREHUB_REMOTE_MCP_URL` | No | `https://mcp.scorehub.cn` | Base URL of the remote ScoreHub MCP endpoint |
95
+
96
+ ### Token storage
97
+
98
+ OAuth tokens are stored at:
99
+
100
+ ```
101
+ ~/.scorehub/<host>/oauth.json
102
+ ```
103
+
104
+ The `<host>` is derived from the `SCOREHUB_REMOTE_MCP_URL` (e.g., `mcp.scorehub.cn`). This means you can run staging and production side-by-side by pointing each configuration at a different URL — tokens are isolated per host.
105
+
106
+ **Token file structure:**
107
+
108
+ ```json
109
+ {
110
+ "access_token": "eyJ...",
111
+ "refresh_token": "eyJ...",
112
+ "expires_at": 1710000000000,
113
+ "updated_at": "2026-07-10T08:00:00.000Z"
114
+ }
115
+ ```
116
+
117
+ To log out or switch accounts, delete the `~/.scorehub/` directory or just the host-specific subdirectory.
118
+
119
+ ## Available Tools
120
+
121
+ ### Local tools
122
+
123
+ These tools run locally on your machine — they do not call the remote endpoint.
124
+
125
+ | Tool | Description |
126
+ |---|---|
127
+ | `authorize` | Opens your browser for ScoreHub OAuth login. Forces a fresh authorization flow every time. |
128
+ | `status` | Returns connection status: remote URL, authorization state, and count of available tools. |
129
+
130
+ ### Remote tools
131
+
132
+ These tools are **dynamically loaded** from the remote MCP endpoint at startup and forwarded on every call. The current set includes:
133
+
134
+ | Tool | Description |
135
+ |---|---|
136
+ | `search_creators` | Search TikTok Shop creators by keywords, GMV range, sales volume, category, and other filters |
137
+ | `creator_performance` | Analyze creator performance — 30-day GMV, follower demographics, content engagement data |
138
+ | `get_categories` | Fetch available TikTok Shop product categories |
139
+ | `create_conversation` | Create a collaboration conversation thread with a specific creator |
140
+ | `send_message` | Send a collaboration message (text, product card, collaboration invite, sample request, or image) |
141
+
142
+ > **Note**: The remote tool list is fetched at server startup and refreshed on each `ListTools` request. If new tools are added to the remote endpoint, they become available without updating the local package.
143
+
144
+ ## Authentication Flow
145
+
146
+ ```
147
+ Local mcp-server ScoreHub Remote
148
+ │ │
149
+ ├─ tools/call: authorize ──────────┤
150
+ │ (clears any existing token) │
151
+ │ │
152
+ ├─ generates random state │
153
+ ├─ opens browser → /authorize ────▶│
154
+ │ user logs in + grants access
155
+ │ ◀── redirect to localhost:19836 │
156
+ │ with authorization_code │
157
+ │ │
158
+ ├─ POST /token ───────────────────▶│
159
+ │◀── access_token + refresh_token │
160
+ │ │
161
+ ├─ saves to ~/.scorehub/<host>/oauth.json
162
+ ```
163
+
164
+ ### Token lifecycle
165
+
166
+ | Stage | Behavior |
167
+ |---|---|
168
+ | **First use** | Browser OAuth → callback → write token to local file |
169
+ | **Subsequent calls** | Read cached file → check expiry with 5-minute buffer |
170
+ | **Near expiry** | Auto-refresh with `refresh_token` → write new token back to file |
171
+ | **Refresh fails** | Returns an error message with the authorization URL — call `authorize` again |
172
+ | **Manual logout** | Delete `~/.scorehub/<host>/oauth.json` or the entire `~/.scorehub/` directory |
173
+
174
+ ### Headless environments
175
+
176
+ If you're running on a server or in a headless environment where a browser cannot open automatically, the authorization URL will be printed to stderr. Copy it and open it manually in any browser. After login, the callback goes to `http://127.0.0.1:19836/callback` — make sure port 19836 is accessible on the machine running mcp-server.
177
+
178
+ ## Architecture
179
+
180
+ ```
181
+ ┌──────────────────────────────────────────────────────────────────┐
182
+ │ YOUR MACHINE │
183
+ │ │
184
+ │ ┌──────────────┐ stdio ┌─────────────────────┐ │
185
+ │ │ Claude Code │ ◄────────► │ @scorehub/mcp-server │ │
186
+ │ │ WorkBuddy │ │ │ │
187
+ │ │ Codex │ │ • OAuth login │ │
188
+ │ │ (any MCP │ │ • Token cache │ │
189
+ │ │ client) │ │ • Tool forwarding │ │
190
+ │ └──────────────┘ └─────────┬───────────┘ │
191
+ │ │ │
192
+ │ ~/.scorehub/<host>/oauth.json │
193
+ └────────────────────────────────────────┼──────────────────────────┘
194
+ │ Streamable HTTP
195
+ ▼
196
+ ┌─────────────────────┐
197
+ │ ScoreHub Remote MCP │
198
+ │ (mcp.scorehub.cn) │
199
+ └─────────┬───────────┘
200
+ │
201
+ ▼
202
+ ┌─────────────────────┐
203
+ │ TikTok Shop │
204
+ │ Partner API │
205
+ └─────────────────────┘
206
+ ```
207
+
208
+ ### Why a local proxy?
209
+
210
+ - **Browser-based OAuth** — The OAuth flow requires opening a browser on your machine. A local server can launch your browser, whereas a remote server cannot.
211
+ - **Token stays local** — Your access token never leaves your machine. The remote endpoint only sees it in the `Authorization` header of forwarded requests.
212
+ - **Cross-platform** — Automatically uses `open` (macOS), `start` (Windows), or `xdg-open` (Linux) to launch the browser.
213
+ - **Minimal dependencies** — Only depends on `@modelcontextprotocol/sdk` (~1 MB). No database, no framework, no configuration files.
214
+
215
+ ## FAQ
216
+
217
+ ### Why do I need this package?
218
+
219
+ If you're using `@scorehub/creator-outreach` (the Tiky agent for TikTok creator outreach), this package is a **required prerequisite**. It provides the MCP tools the agent needs to search creators, analyze performance, and send messages.
220
+
221
+ ### Can I use this without @scorehub/creator-outreach?
222
+
223
+ Yes, absolutely. `@scorehub/mcp-server` is a standalone MCP server. Any MCP-compatible client can use its tools directly.
224
+
225
+ ### How do I log out?
226
+
227
+ Delete the token file:
228
+
229
+ ```bash
230
+ rm ~/.scorehub/mcp.scorehub.cn/oauth.json
231
+ ```
232
+
233
+ Or delete the entire directory to clear all hosts:
234
+
235
+ ```bash
236
+ rm -rf ~/.scorehub/
237
+ ```
238
+
239
+ ### How do I switch between staging and production?
240
+
241
+ Set different `SCOREHUB_REMOTE_MCP_URL` values in separate MCP client configurations. Tokens are isolated per host, so staging and production credentials won't interfere with each other.
242
+
243
+ ### What happens if the remote MCP is unreachable?
244
+
245
+ - Local tools (`authorize`, `status`) continue to work
246
+ - Remote tools return an error: `"Warning: Could not fetch remote tools"`
247
+ - The server retries fetching the tool list on each `ListTools` request
248
+ - Once the remote endpoint is reachable again, remote tools become available without restarting
249
+
250
+ ### How do I upgrade?
251
+
252
+ ```bash
253
+ npm update -g @scorehub/mcp-server
254
+ ```
255
+
256
+ Or, if you're using `npx -y`, it will always fetch the latest version automatically.
257
+
258
+ ## Related Packages
259
+
260
+ | Package | Description |
261
+ |---|---|
262
+ | [`@scorehub/creator-outreach`](https://www.npmjs.com/package/@scorehub/creator-outreach) | Tiky agent — AI-powered TikTok Shop creator outreach (search, analyze, message) |
263
+ | `scorehub-mcp-remote` | Remote MCP endpoint (private — deployed on ScoreHub servers) |
264
+
265
+ ## License
266
+
267
+ [MIT](./LICENSE)
package/dist/index.js CHANGED
@@ -14,51 +14,52 @@ Object.defineProperty(exports, "__esModule", { value: true });
14
14
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
15
15
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
16
16
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
17
+ const index_js_2 = require("@modelcontextprotocol/sdk/client/index.js");
18
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
17
19
  const REMOTE_MCP_URL = process.env.SCOREHUB_REMOTE_MCP_URL || "https://mcp.scorehub.cn";
18
20
  const TOKEN_ENDPOINT = `${REMOTE_MCP_URL}/token`;
19
21
  const AUTHORIZE_URL = `${REMOTE_MCP_URL}/authorize`;
20
22
  // ── Auth State ──────────────────────────────────────────────
21
23
  const auth_js_1 = require("./auth.js");
22
24
  const node_child_process_1 = require("node:child_process");
25
+ const node_os_1 = require("node:os");
26
+ const node_path_1 = require("node:path");
27
+ const node_fs_1 = require("node:fs");
23
28
  const HOST = new URL(REMOTE_MCP_URL).host;
24
29
  const CLIENT_ID = "scorehub-mcp";
25
- // ── Remote Tool Proxy ───────────────────────────────────────
26
- async function fetchRemoteTools() {
30
+ // ── Remote Tool Proxy (MCP Streamable HTTP) ─────────────────
31
+ async function fetchRemoteTools(token) {
27
32
  try {
28
- const resp = await fetch(REMOTE_MCP_URL.replace(/\/$/, "") + "/mcp/tools/list");
29
- if (!resp.ok)
30
- throw new Error(`Remote tools fetch failed: ${resp.status}`);
31
- const data = await resp.json();
32
- return data.tools || [];
33
+ const transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(REMOTE_MCP_URL), {
34
+ requestInit: token ? { headers: { Authorization: `Bearer ${token}` } } : undefined,
35
+ });
36
+ const client = new index_js_2.Client({ name: "scorehub-mcp", version: "0.2.0" }, { capabilities: {} });
37
+ try {
38
+ await client.connect(transport);
39
+ const result = await client.listTools();
40
+ return result.tools;
41
+ }
42
+ finally {
43
+ await client.close();
44
+ }
33
45
  }
34
46
  catch {
35
- // Return offline message when remote is not available
36
47
  return [];
37
48
  }
38
49
  }
39
50
  async function callRemoteTool(name, args, token) {
40
- // Map tool names to remote endpoints
41
- const toolEndpoints = {
42
- search_creators: "/mcp/tools/search-creators",
43
- creator_performance: "/mcp/tools/creator-performance",
44
- create_conversation: "/mcp/tools/create-conversation",
45
- send_message: "/mcp/tools/send-message",
46
- get_categories: "/mcp/tools/get-categories",
47
- };
48
- const endpoint = toolEndpoints[name];
49
- if (!endpoint) {
50
- return JSON.stringify({ error: `Unknown tool: ${name}` });
51
- }
52
- const resp = await fetch(`${REMOTE_MCP_URL}${endpoint}`, {
53
- method: "POST",
54
- headers: {
55
- "Content-Type": "application/json",
56
- ...(token ? { Authorization: `Bearer ${token}` } : {}),
57
- },
58
- body: JSON.stringify(args),
51
+ const transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(REMOTE_MCP_URL), {
52
+ requestInit: { headers: { Authorization: `Bearer ${token}` } },
59
53
  });
60
- const data = await resp.json();
61
- return JSON.stringify(data, null, 2);
54
+ const client = new index_js_2.Client({ name: "scorehub-mcp", version: "0.2.0" }, { capabilities: {} });
55
+ try {
56
+ await client.connect(transport);
57
+ const result = await client.callTool({ name, arguments: args });
58
+ return JSON.stringify(result.content, null, 2);
59
+ }
60
+ finally {
61
+ await client.close();
62
+ }
62
63
  }
63
64
  // ── OAuth authorize helper ──────────────────────────────────
64
65
  function generateAuthUrl() {
@@ -191,6 +192,29 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
191
192
  });
192
193
  // ── Start ───────────────────────────────────────────────────
193
194
  async function main() {
195
+ // ── Clean up orphaned previous instance ────────────────────
196
+ const pidFile = (0, node_path_1.join)((0, node_os_1.homedir)(), ".scorehub", HOST, "pid");
197
+ try {
198
+ if ((0, node_fs_1.existsSync)(pidFile)) {
199
+ const oldPid = parseInt((0, node_fs_1.readFileSync)(pidFile, "utf-8").trim(), 10);
200
+ if (oldPid && oldPid !== process.pid) {
201
+ try {
202
+ process.kill(oldPid, 0); // check if process exists (cross-platform)
203
+ process.kill(oldPid); // SIGTERM on POSIX, TerminateProcess on Windows
204
+ }
205
+ catch { /* process already dead */ }
206
+ }
207
+ }
208
+ }
209
+ catch { /* ignore PID file read errors */ }
210
+ // Write current PID
211
+ try {
212
+ const dir = (0, node_path_1.dirname)(pidFile);
213
+ if (!(0, node_fs_1.existsSync)(dir))
214
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
215
+ (0, node_fs_1.writeFileSync)(pidFile, String(process.pid), "utf-8");
216
+ }
217
+ catch { /* ignore PID file write errors */ }
194
218
  try {
195
219
  remoteTools = await fetchRemoteTools();
196
220
  }
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "name": "@scorehub/mcp-server",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "ScoreHub local MCP proxy — OAuth login, token caching, and tool forwarding to remote MCP endpoint",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "scorehub-mcp": "./dist/index.js"
8
8
  },
9
9
  "files": [
10
- "dist"
10
+ "dist",
11
+ "README.md"
11
12
  ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
12
16
  "scripts": {
13
17
  "build": "tsc && node -e \"const fs=require('fs');const f='dist/index.js';fs.writeFileSync(f,'#!/usr/bin/env node\\n'+fs.readFileSync(f,'utf8'));fs.chmodSync(f,0o755)\"",
14
18
  "dev": "tsx src/index.ts",