@bycrawl/mcp 0.4.1 → 0.5.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/README.md +20 -0
- package/build/client.js +5 -1
- package/build/client.js.map +1 -1
- package/build/oauth/callback.d.ts +6 -0
- package/build/oauth/callback.js +75 -0
- package/build/oauth/callback.js.map +1 -0
- package/build/oauth/crypto.d.ts +6 -0
- package/build/oauth/crypto.js +29 -0
- package/build/oauth/crypto.js.map +1 -0
- package/build/oauth/provider.d.ts +14 -0
- package/build/oauth/provider.js +125 -0
- package/build/oauth/provider.js.map +1 -0
- package/build/oauth/store.d.ts +43 -0
- package/build/oauth/store.js +66 -0
- package/build/oauth/store.js.map +1 -0
- package/build/oauth/user-keys.d.ts +6 -0
- package/build/oauth/user-keys.js +89 -0
- package/build/oauth/user-keys.js.map +1 -0
- package/build/server.js +2 -2
- package/build/server.js.map +1 -1
- package/build/tools/dcard.js +6 -14
- package/build/tools/dcard.js.map +1 -1
- package/build/tools/facebook.js +21 -8
- package/build/tools/facebook.js.map +1 -1
- package/build/tools/gmaps.d.ts +2 -0
- package/build/tools/gmaps.js +13 -0
- package/build/tools/gmaps.js.map +1 -0
- package/build/tools/instagram.js +5 -5
- package/build/tools/instagram.js.map +1 -1
- package/build/tools/job104.js +3 -3
- package/build/tools/job104.js.map +1 -1
- package/build/tools/linkedin.js +9 -9
- package/build/tools/linkedin.js.map +1 -1
- package/build/tools/reddit.js +16 -5
- package/build/tools/reddit.js.map +1 -1
- package/build/tools/system.js +2 -1
- package/build/tools/system.js.map +1 -1
- package/build/tools/threads.js +8 -8
- package/build/tools/threads.js.map +1 -1
- package/build/tools/tiktok.js +8 -8
- package/build/tools/tiktok.js.map +1 -1
- package/build/tools/x.js +5 -5
- package/build/tools/x.js.map +1 -1
- package/build/tools/youtube.js +5 -7
- package/build/tools/youtube.js.map +1 -1
- package/build/transport-http.js +146 -66
- package/build/transport-http.js.map +1 -1
- package/package.json +6 -6
- package/build/tools/xiaohongshu.d.ts +0 -2
- package/build/tools/xiaohongshu.js +0 -31
- package/build/tools/xiaohongshu.js.map +0 -1
package/README.md
CHANGED
|
@@ -45,6 +45,26 @@ Add to your MCP config:
|
|
|
45
45
|
| Job104 | 3 | Search jobs, get company/job details |
|
|
46
46
|
| System | 1 | Health check |
|
|
47
47
|
|
|
48
|
+
## Usage Examples
|
|
49
|
+
|
|
50
|
+
### Example 1: Search Threads posts about a topic
|
|
51
|
+
|
|
52
|
+
> **Prompt:** "What are people saying about OpenAI on Threads?"
|
|
53
|
+
|
|
54
|
+
Claude will call `threads_search_posts` with `query: "OpenAI"` and return a summary of recent public posts, including text, like counts, and reply counts.
|
|
55
|
+
|
|
56
|
+
### Example 2: Get LinkedIn company info
|
|
57
|
+
|
|
58
|
+
> **Prompt:** "Tell me about Anthropic's LinkedIn presence — what do they do, how big are they?"
|
|
59
|
+
|
|
60
|
+
Claude will call `linkedin_get_company` with `company_id: "anthropic"` and return the company's description, employee count, industry, headquarters, and recent activity.
|
|
61
|
+
|
|
62
|
+
### Example 3: Fetch YouTube video comments
|
|
63
|
+
|
|
64
|
+
> **Prompt:** "Summarize the comments on this YouTube video: https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
|
65
|
+
|
|
66
|
+
Claude will call `youtube_get_video_comments` with `video_id: "dQw4w9WgXcQ"` and return the top comments with like counts, which it can then summarize for sentiment and key themes.
|
|
67
|
+
|
|
48
68
|
## API Key
|
|
49
69
|
|
|
50
70
|
Get your API key at [bycrawl.com](https://bycrawl.com).
|
package/build/client.js
CHANGED
|
@@ -9,15 +9,19 @@ export async function bycrawlGet(path, params) {
|
|
|
9
9
|
url.searchParams.set(k, String(v));
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
const apiKey = getApiKey();
|
|
13
|
+
console.log(`[bycrawlGet] ${url.toString()} apiKey=${apiKey ? apiKey.slice(0, 12) + "..." : "(empty)"}`);
|
|
12
14
|
const res = await fetch(url, {
|
|
13
|
-
headers: { "x-api-key":
|
|
15
|
+
headers: { "x-api-key": apiKey },
|
|
14
16
|
signal: AbortSignal.timeout(120_000),
|
|
15
17
|
});
|
|
16
18
|
const body = await res.text();
|
|
19
|
+
console.log(`[bycrawlGet] status=${res.status} body=${body.slice(0, 200)}`);
|
|
17
20
|
return { content: [{ type: "text", text: body }] };
|
|
18
21
|
}
|
|
19
22
|
catch (err) {
|
|
20
23
|
const message = err instanceof Error ? err.message : String(err);
|
|
24
|
+
console.error(`[bycrawlGet] error:`, message);
|
|
21
25
|
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }] };
|
|
22
26
|
}
|
|
23
27
|
}
|
package/build/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,yBAAyB,CAAC;AAIzE,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAY,EACZ,MAAqE;IAErE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACnC,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE;oBAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,OAAO,EAAE,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,yBAAyB,CAAC;AAIzE,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAY,EACZ,MAAqE;IAErE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACnC,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE;oBAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,CAAC,QAAQ,EAAE,WAAW,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QAEzG,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;YAChC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC;SACrC,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,uBAAuB,GAAG,CAAC,MAAM,SAAS,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5E,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACrD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,OAAO,CAAC,CAAC;QAC9C,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;IACnF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { pendingAuthStore, authCodeStore } from "./store.js";
|
|
3
|
+
import { findUserByEmail, findOrCreateMcpKey } from "./user-keys.js";
|
|
4
|
+
const SUPABASE_URL = process.env.SUPABASE_URL;
|
|
5
|
+
const SUPABASE_PUBLISHABLE_KEY = process.env.SUPABASE_PUBLISHABLE_KEY;
|
|
6
|
+
/**
|
|
7
|
+
* GET /oauth/callback/:nonce
|
|
8
|
+
* Supabase Auth redirects here after Google login.
|
|
9
|
+
*/
|
|
10
|
+
export async function oauthCallbackHandler(req, res) {
|
|
11
|
+
try {
|
|
12
|
+
const nonce = req.params.nonce;
|
|
13
|
+
const code = req.query.code;
|
|
14
|
+
if (!nonce || !code) {
|
|
15
|
+
res.status(400).json({ error: "Missing nonce or code" });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// 1. Look up pending auth state
|
|
19
|
+
const pending = pendingAuthStore.getAndDelete(nonce);
|
|
20
|
+
if (!pending) {
|
|
21
|
+
res.status(400).json({ error: "Invalid or expired nonce" });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// 2. Exchange Supabase auth code for session (PKCE flow)
|
|
25
|
+
const tokenRes = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=pkce`, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: {
|
|
28
|
+
apikey: SUPABASE_PUBLISHABLE_KEY,
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
auth_code: code,
|
|
33
|
+
code_verifier: pending.supabaseCodeVerifier,
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
if (!tokenRes.ok) {
|
|
37
|
+
const err = await tokenRes.text();
|
|
38
|
+
res.status(502).json({ error: `Supabase token exchange failed: ${err}` });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const session = (await tokenRes.json());
|
|
42
|
+
const email = session.user?.email;
|
|
43
|
+
if (!email) {
|
|
44
|
+
res.status(502).json({ error: "No email in Supabase session" });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// 3. Find user in ByCrawl
|
|
48
|
+
const user = await findUserByEmail(email);
|
|
49
|
+
if (!user) {
|
|
50
|
+
const loginUrl = `https://bycrawl.com/login?reason=mcp_signup_required&email=${encodeURIComponent(email)}`;
|
|
51
|
+
res.redirect(302, loginUrl);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// 4. Find or create MCP API key
|
|
55
|
+
const apiKey = await findOrCreateMcpKey(user.id);
|
|
56
|
+
// 5. Generate auth code and store it
|
|
57
|
+
const authCode = randomBytes(32).toString("hex");
|
|
58
|
+
authCodeStore.set(authCode, {
|
|
59
|
+
clientId: pending.clientId,
|
|
60
|
+
apiKey,
|
|
61
|
+
codeChallenge: pending.codeChallenge,
|
|
62
|
+
redirectUri: pending.redirectUri,
|
|
63
|
+
});
|
|
64
|
+
// 6. Redirect back to Claude with auth code
|
|
65
|
+
const redirectUrl = new URL(pending.redirectUri);
|
|
66
|
+
redirectUrl.searchParams.set("code", authCode);
|
|
67
|
+
redirectUrl.searchParams.set("state", pending.state);
|
|
68
|
+
res.redirect(302, redirectUrl.toString());
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error("OAuth callback error:", err);
|
|
72
|
+
res.status(500).json({ error: "Internal server error during OAuth callback" });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=callback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback.js","sourceRoot":"","sources":["../../src/oauth/callback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** AES-256-GCM encrypt. Returns `iv:ciphertext:tag` in hex. */
|
|
2
|
+
export declare function encryptApiKey(plain: string): string;
|
|
3
|
+
/** Decrypt an `iv:ciphertext:tag` hex blob. */
|
|
4
|
+
export declare function decryptApiKey(blob: string): string;
|
|
5
|
+
/** SHA-256 hex hash — matches ByCrawl gateway's Python hashlib.sha256. */
|
|
6
|
+
export declare function hashApiKey(plain: string): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
|
|
2
|
+
const KEY_HEX = process.env.KEY_ENCRYPTION_KEY ?? "";
|
|
3
|
+
function getKey() {
|
|
4
|
+
if (KEY_HEX.length !== 64)
|
|
5
|
+
throw new Error("KEY_ENCRYPTION_KEY must be 64 hex chars (32 bytes)");
|
|
6
|
+
return Buffer.from(KEY_HEX, "hex");
|
|
7
|
+
}
|
|
8
|
+
/** AES-256-GCM encrypt. Returns `iv:ciphertext:tag` in hex. */
|
|
9
|
+
export function encryptApiKey(plain) {
|
|
10
|
+
const key = getKey();
|
|
11
|
+
const iv = randomBytes(12);
|
|
12
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
13
|
+
const encrypted = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
|
|
14
|
+
const tag = cipher.getAuthTag();
|
|
15
|
+
return `${iv.toString("hex")}:${encrypted.toString("hex")}:${tag.toString("hex")}`;
|
|
16
|
+
}
|
|
17
|
+
/** Decrypt an `iv:ciphertext:tag` hex blob. */
|
|
18
|
+
export function decryptApiKey(blob) {
|
|
19
|
+
const key = getKey();
|
|
20
|
+
const [ivHex, ctHex, tagHex] = blob.split(":");
|
|
21
|
+
const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(ivHex, "hex"));
|
|
22
|
+
decipher.setAuthTag(Buffer.from(tagHex, "hex"));
|
|
23
|
+
return decipher.update(ctHex, "hex", "utf8") + decipher.final("utf8");
|
|
24
|
+
}
|
|
25
|
+
/** SHA-256 hex hash — matches ByCrawl gateway's Python hashlib.sha256. */
|
|
26
|
+
export function hashApiKey(plain) {
|
|
27
|
+
return createHash("sha256").update(plain).digest("hex");
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/oauth/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAExF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC;AAErD,SAAS,MAAM;IACb,IAAI,OAAO,CAAC,MAAM,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACjG,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAChF,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAChC,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;AACrF,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IACjF,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IAChD,OAAO,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AACxE,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Response } from "express";
|
|
2
|
+
import type { OAuthServerProvider, AuthorizationParams } from "@modelcontextprotocol/sdk/server/auth/provider.js";
|
|
3
|
+
import type { OAuthRegisteredClientsStore } from "@modelcontextprotocol/sdk/server/auth/clients.js";
|
|
4
|
+
import type { OAuthClientInformationFull, OAuthTokens, OAuthTokenRevocationRequest } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
5
|
+
import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
|
|
6
|
+
export declare class ByCrawlOAuthProvider implements OAuthServerProvider {
|
|
7
|
+
get clientsStore(): OAuthRegisteredClientsStore;
|
|
8
|
+
authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void>;
|
|
9
|
+
challengeForAuthorizationCode(_client: OAuthClientInformationFull, authorizationCode: string): Promise<string>;
|
|
10
|
+
exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string): Promise<OAuthTokens>;
|
|
11
|
+
exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string): Promise<OAuthTokens>;
|
|
12
|
+
verifyAccessToken(token: string): Promise<AuthInfo>;
|
|
13
|
+
revokeToken(_client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { randomBytes, createHash } from "node:crypto";
|
|
2
|
+
import { SignJWT, jwtVerify } from "jose";
|
|
3
|
+
import { pendingAuthStore, authCodeStore, refreshTokenStore, clientStore, } from "./store.js";
|
|
4
|
+
const SUPABASE_URL = process.env.SUPABASE_URL;
|
|
5
|
+
const EXTERNAL_URL = process.env.EXTERNAL_URL;
|
|
6
|
+
const JWT_SECRET = process.env.JWT_SECRET;
|
|
7
|
+
function getJwtKey() {
|
|
8
|
+
return new TextEncoder().encode(JWT_SECRET);
|
|
9
|
+
}
|
|
10
|
+
async function signJwt(apiKey, clientId) {
|
|
11
|
+
return new SignJWT({ api_key: apiKey, client_id: clientId })
|
|
12
|
+
.setProtectedHeader({ alg: "HS256" })
|
|
13
|
+
.setIssuedAt()
|
|
14
|
+
.setExpirationTime("1h")
|
|
15
|
+
.sign(getJwtKey());
|
|
16
|
+
}
|
|
17
|
+
export class ByCrawlOAuthProvider {
|
|
18
|
+
get clientsStore() {
|
|
19
|
+
return clientStore;
|
|
20
|
+
}
|
|
21
|
+
async authorize(client, params, res) {
|
|
22
|
+
const nonce = randomBytes(16).toString("hex");
|
|
23
|
+
// Generate PKCE pair for Supabase side
|
|
24
|
+
const supabaseCodeVerifier = randomBytes(32).toString("hex");
|
|
25
|
+
const supabaseCodeChallenge = createHash("sha256")
|
|
26
|
+
.update(supabaseCodeVerifier)
|
|
27
|
+
.digest("base64url");
|
|
28
|
+
pendingAuthStore.set(nonce, {
|
|
29
|
+
state: params.state ?? "",
|
|
30
|
+
clientId: client.client_id,
|
|
31
|
+
codeChallenge: params.codeChallenge,
|
|
32
|
+
redirectUri: params.redirectUri,
|
|
33
|
+
scopes: params.scopes ?? [],
|
|
34
|
+
supabaseCodeVerifier,
|
|
35
|
+
});
|
|
36
|
+
// Redirect to Supabase Auth (Google OAuth) with PKCE + nonce in path
|
|
37
|
+
const callbackUrl = `${EXTERNAL_URL}/oauth/callback/${nonce}`;
|
|
38
|
+
const supabaseAuthUrl = `${SUPABASE_URL}/auth/v1/authorize?provider=google&flow_type=pkce&code_challenge=${supabaseCodeChallenge}&code_challenge_method=S256&redirect_to=${encodeURIComponent(callbackUrl)}`;
|
|
39
|
+
res.redirect(302, supabaseAuthUrl);
|
|
40
|
+
}
|
|
41
|
+
async challengeForAuthorizationCode(_client, authorizationCode) {
|
|
42
|
+
const entry = authCodeStore.get(authorizationCode);
|
|
43
|
+
if (!entry)
|
|
44
|
+
throw new Error("Invalid authorization code");
|
|
45
|
+
return entry.codeChallenge;
|
|
46
|
+
}
|
|
47
|
+
async exchangeAuthorizationCode(client, authorizationCode) {
|
|
48
|
+
const entry = authCodeStore.getAndDelete(authorizationCode);
|
|
49
|
+
if (!entry)
|
|
50
|
+
throw new Error("Invalid or expired authorization code");
|
|
51
|
+
if (entry.clientId !== client.client_id)
|
|
52
|
+
throw new Error("Client ID mismatch");
|
|
53
|
+
const accessToken = await signJwt(entry.apiKey, client.client_id);
|
|
54
|
+
const refreshToken = randomBytes(32).toString("hex");
|
|
55
|
+
refreshTokenStore.set(refreshToken, {
|
|
56
|
+
clientId: client.client_id,
|
|
57
|
+
apiKey: entry.apiKey,
|
|
58
|
+
scopes: [],
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
access_token: accessToken,
|
|
62
|
+
token_type: "bearer",
|
|
63
|
+
expires_in: 3600,
|
|
64
|
+
refresh_token: refreshToken,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async exchangeRefreshToken(client, refreshToken) {
|
|
68
|
+
const entry = refreshTokenStore.getAndDelete(refreshToken);
|
|
69
|
+
if (!entry)
|
|
70
|
+
throw new Error("Invalid or expired refresh token");
|
|
71
|
+
if (entry.clientId !== client.client_id)
|
|
72
|
+
throw new Error("Client ID mismatch");
|
|
73
|
+
const accessToken = await signJwt(entry.apiKey, client.client_id);
|
|
74
|
+
const newRefreshToken = randomBytes(32).toString("hex");
|
|
75
|
+
// Rotate: store new refresh token
|
|
76
|
+
refreshTokenStore.set(newRefreshToken, {
|
|
77
|
+
clientId: client.client_id,
|
|
78
|
+
apiKey: entry.apiKey,
|
|
79
|
+
scopes: entry.scopes,
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
access_token: accessToken,
|
|
83
|
+
token_type: "bearer",
|
|
84
|
+
expires_in: 3600,
|
|
85
|
+
refresh_token: newRefreshToken,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async verifyAccessToken(token) {
|
|
89
|
+
console.log(`[verifyAccessToken] token prefix=${token.slice(0, 20)}...`);
|
|
90
|
+
// Backward compat: plain API keys bypass JWT verification
|
|
91
|
+
if (token.startsWith("sk_byc_")) {
|
|
92
|
+
console.log(`[verifyAccessToken] plain API key detected`);
|
|
93
|
+
return {
|
|
94
|
+
token,
|
|
95
|
+
clientId: "legacy",
|
|
96
|
+
scopes: [],
|
|
97
|
+
expiresAt: Math.floor(Date.now() / 1000) + 3600,
|
|
98
|
+
extra: { apiKey: token },
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// JWT verification
|
|
102
|
+
try {
|
|
103
|
+
const { payload } = await jwtVerify(token, getJwtKey(), {
|
|
104
|
+
algorithms: ["HS256"],
|
|
105
|
+
});
|
|
106
|
+
const apiKey = payload.api_key;
|
|
107
|
+
console.log(`[verifyAccessToken] JWT valid, clientId=${payload.client_id}, apiKey prefix=${apiKey?.slice(0, 12)}`);
|
|
108
|
+
return {
|
|
109
|
+
token,
|
|
110
|
+
clientId: payload.client_id ?? "unknown",
|
|
111
|
+
scopes: [],
|
|
112
|
+
expiresAt: payload.exp,
|
|
113
|
+
extra: { apiKey },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.error(`[verifyAccessToken] JWT verification failed:`, err);
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async revokeToken(_client, request) {
|
|
122
|
+
refreshTokenStore.delete(request.token);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/oauth/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { OAuthRegisteredClientsStore } from "@modelcontextprotocol/sdk/server/auth/clients.js";
|
|
2
|
+
import type { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
3
|
+
declare class TtlMap<V> {
|
|
4
|
+
private ttlMs;
|
|
5
|
+
private map;
|
|
6
|
+
private timer;
|
|
7
|
+
constructor(ttlMs: number);
|
|
8
|
+
set(key: string, value: V): void;
|
|
9
|
+
get(key: string): V | undefined;
|
|
10
|
+
getAndDelete(key: string): V | undefined;
|
|
11
|
+
delete(key: string): void;
|
|
12
|
+
private cleanup;
|
|
13
|
+
destroy(): void;
|
|
14
|
+
}
|
|
15
|
+
export interface PendingAuth {
|
|
16
|
+
state: string;
|
|
17
|
+
clientId: string;
|
|
18
|
+
codeChallenge: string;
|
|
19
|
+
redirectUri: string;
|
|
20
|
+
scopes: string[];
|
|
21
|
+
supabaseCodeVerifier: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const pendingAuthStore: TtlMap<PendingAuth>;
|
|
24
|
+
export interface AuthCode {
|
|
25
|
+
clientId: string;
|
|
26
|
+
apiKey: string;
|
|
27
|
+
codeChallenge: string;
|
|
28
|
+
redirectUri: string;
|
|
29
|
+
}
|
|
30
|
+
export declare const authCodeStore: TtlMap<AuthCode>;
|
|
31
|
+
export interface RefreshEntry {
|
|
32
|
+
clientId: string;
|
|
33
|
+
apiKey: string;
|
|
34
|
+
scopes: string[];
|
|
35
|
+
}
|
|
36
|
+
export declare const refreshTokenStore: TtlMap<RefreshEntry>;
|
|
37
|
+
declare class InMemoryClientStore implements OAuthRegisteredClientsStore {
|
|
38
|
+
private clients;
|
|
39
|
+
getClient(clientId: string): OAuthClientInformationFull | undefined;
|
|
40
|
+
registerClient(client: Omit<OAuthClientInformationFull, "client_id" | "client_id_issued_at">): OAuthClientInformationFull;
|
|
41
|
+
}
|
|
42
|
+
export declare const clientStore: InMemoryClientStore;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
// --- TTL Map utility ---
|
|
3
|
+
class TtlMap {
|
|
4
|
+
ttlMs;
|
|
5
|
+
map = new Map();
|
|
6
|
+
timer;
|
|
7
|
+
constructor(ttlMs) {
|
|
8
|
+
this.ttlMs = ttlMs;
|
|
9
|
+
this.timer = setInterval(() => this.cleanup(), 60_000);
|
|
10
|
+
this.timer.unref();
|
|
11
|
+
}
|
|
12
|
+
set(key, value) {
|
|
13
|
+
this.map.set(key, { value, expiresAt: Date.now() + this.ttlMs });
|
|
14
|
+
}
|
|
15
|
+
get(key) {
|
|
16
|
+
const entry = this.map.get(key);
|
|
17
|
+
if (!entry)
|
|
18
|
+
return undefined;
|
|
19
|
+
if (Date.now() > entry.expiresAt) {
|
|
20
|
+
this.map.delete(key);
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return entry.value;
|
|
24
|
+
}
|
|
25
|
+
getAndDelete(key) {
|
|
26
|
+
const value = this.get(key);
|
|
27
|
+
if (value !== undefined)
|
|
28
|
+
this.map.delete(key);
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
delete(key) {
|
|
32
|
+
this.map.delete(key);
|
|
33
|
+
}
|
|
34
|
+
cleanup() {
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
for (const [key, entry] of this.map) {
|
|
37
|
+
if (now > entry.expiresAt)
|
|
38
|
+
this.map.delete(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
destroy() {
|
|
42
|
+
clearInterval(this.timer);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const pendingAuthStore = new TtlMap(10 * 60 * 1000); // 10 min
|
|
46
|
+
export const authCodeStore = new TtlMap(5 * 60 * 1000); // 5 min
|
|
47
|
+
export const refreshTokenStore = new TtlMap(30 * 24 * 60 * 60 * 1000); // 30 days
|
|
48
|
+
// --- Client registration (in-memory, persists until restart) ---
|
|
49
|
+
class InMemoryClientStore {
|
|
50
|
+
clients = new Map();
|
|
51
|
+
getClient(clientId) {
|
|
52
|
+
return this.clients.get(clientId);
|
|
53
|
+
}
|
|
54
|
+
registerClient(client) {
|
|
55
|
+
const full = {
|
|
56
|
+
...client,
|
|
57
|
+
client_id: randomUUID(),
|
|
58
|
+
client_id_issued_at: Math.floor(Date.now() / 1000),
|
|
59
|
+
client_secret_expires_at: 0, // never expires
|
|
60
|
+
};
|
|
61
|
+
this.clients.set(full.client_id, full);
|
|
62
|
+
return full;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export const clientStore = new InMemoryClientStore();
|
|
66
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/oauth/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Look up a user by email in user_profiles. */
|
|
2
|
+
export declare function findUserByEmail(email: string): Promise<{
|
|
3
|
+
id: string;
|
|
4
|
+
} | null>;
|
|
5
|
+
/** Find existing MCP key or create a new one. Returns the plain API key. */
|
|
6
|
+
export declare function findOrCreateMcpKey(userId: string): Promise<string>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { encryptApiKey, hashApiKey } from "./crypto.js";
|
|
3
|
+
/** All read scopes needed by MCP tools — no write/admin scopes. */
|
|
4
|
+
const MCP_SCOPES = [
|
|
5
|
+
// Threads
|
|
6
|
+
"threads:read", "threads:feed",
|
|
7
|
+
// X
|
|
8
|
+
"x:profile", "x:timeline", "x:tweet", "x:search",
|
|
9
|
+
// Facebook
|
|
10
|
+
"fb:profile", "fb:posts", "fb:post", "fb:search", "fb:post_comments",
|
|
11
|
+
"fb:marketplace_browse", "fb:marketplace_search", "fb:marketplace_item",
|
|
12
|
+
// Reddit
|
|
13
|
+
"reddit:subreddit", "reddit:posts", "reddit:search", "reddit:post", "reddit:user",
|
|
14
|
+
// Instagram
|
|
15
|
+
"ig:tags", "ig:user", "ig:post", "ig:post_comments", "ig:user_posts",
|
|
16
|
+
// TikTok
|
|
17
|
+
"tiktok:video", "tiktok:user", "tiktok:videos", "tiktok:categories",
|
|
18
|
+
"tiktok:comments", "tiktok:search", "tiktok:subtitles",
|
|
19
|
+
// LinkedIn
|
|
20
|
+
"linkedin:company", "linkedin:jobs", "linkedin:job", "linkedin:post",
|
|
21
|
+
"linkedin:user", "linkedin:user-search",
|
|
22
|
+
// YouTube
|
|
23
|
+
"youtube:video", "youtube:channel", "youtube:search", "youtube:comments", "youtube:transcription",
|
|
24
|
+
// Dcard
|
|
25
|
+
"dcard:post", "dcard:comments", "dcard:forum", "dcard:forum_posts", "dcard:search", "dcard:persona",
|
|
26
|
+
// Job104
|
|
27
|
+
"job104:search", "job104:company", "job104:job",
|
|
28
|
+
// Google Maps
|
|
29
|
+
"gmaps:search", "gmaps:place",
|
|
30
|
+
];
|
|
31
|
+
const SUPABASE_URL = process.env.SUPABASE_URL;
|
|
32
|
+
const SUPABASE_SECRET_KEY = process.env.SUPABASE_SECRET_KEY;
|
|
33
|
+
function supabaseHeaders() {
|
|
34
|
+
return {
|
|
35
|
+
apikey: SUPABASE_SECRET_KEY,
|
|
36
|
+
Authorization: `Bearer ${SUPABASE_SECRET_KEY}`,
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Look up a user by email in user_profiles. */
|
|
41
|
+
export async function findUserByEmail(email) {
|
|
42
|
+
const url = `${SUPABASE_URL}/rest/v1/user_profiles?email=eq.${encodeURIComponent(email)}&select=id`;
|
|
43
|
+
const res = await fetch(url, { headers: supabaseHeaders() });
|
|
44
|
+
if (!res.ok)
|
|
45
|
+
throw new Error(`Supabase user lookup failed: ${res.status}`);
|
|
46
|
+
const rows = (await res.json());
|
|
47
|
+
return rows.length > 0 ? rows[0] : null;
|
|
48
|
+
}
|
|
49
|
+
/** Find existing MCP key or create a new one. Returns the plain API key. */
|
|
50
|
+
export async function findOrCreateMcpKey(userId) {
|
|
51
|
+
// 1. Look for existing active MCP key with encrypted_key
|
|
52
|
+
const findUrl = `${SUPABASE_URL}/rest/v1/api_keys?user_id=eq.${userId}&name=eq.MCP Connector&is_active=eq.true&select=encrypted_key`;
|
|
53
|
+
const findRes = await fetch(findUrl, { headers: supabaseHeaders() });
|
|
54
|
+
if (!findRes.ok)
|
|
55
|
+
throw new Error(`Supabase key lookup failed: ${findRes.status}`);
|
|
56
|
+
const existing = (await findRes.json());
|
|
57
|
+
if (existing.length > 0 && existing[0].encrypted_key) {
|
|
58
|
+
const { decryptApiKey } = await import("./crypto.js");
|
|
59
|
+
return decryptApiKey(existing[0].encrypted_key);
|
|
60
|
+
}
|
|
61
|
+
// 2. Generate a new key
|
|
62
|
+
const plain = `sk_byc_${randomBytes(32).toString("hex")}`;
|
|
63
|
+
const keyHash = hashApiKey(plain);
|
|
64
|
+
const encrypted = encryptApiKey(plain);
|
|
65
|
+
const keyPrefix = plain.slice(0, 16); // matches dashboard crypto.ts
|
|
66
|
+
const insertUrl = `${SUPABASE_URL}/rest/v1/api_keys`;
|
|
67
|
+
const insertRes = await fetch(insertUrl, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
...supabaseHeaders(),
|
|
71
|
+
Prefer: "return=minimal",
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
user_id: userId,
|
|
75
|
+
name: "MCP Connector",
|
|
76
|
+
key_prefix: keyPrefix,
|
|
77
|
+
key_hash: keyHash,
|
|
78
|
+
encrypted_key: encrypted,
|
|
79
|
+
scopes: MCP_SCOPES,
|
|
80
|
+
is_active: true,
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
if (!insertRes.ok) {
|
|
84
|
+
const body = await insertRes.text();
|
|
85
|
+
throw new Error(`Supabase key insert failed: ${insertRes.status} ${body}`);
|
|
86
|
+
}
|
|
87
|
+
return plain;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=user-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-keys.js","sourceRoot":"","sources":["../../src/oauth/user-keys.ts"],"names":[],"mappings":"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"}
|
package/build/server.js
CHANGED
|
@@ -4,13 +4,13 @@ import { registerXTools } from "./tools/x.js";
|
|
|
4
4
|
import { registerFacebookTools } from "./tools/facebook.js";
|
|
5
5
|
import { registerRedditTools } from "./tools/reddit.js";
|
|
6
6
|
import { registerLinkedinTools } from "./tools/linkedin.js";
|
|
7
|
-
import { registerXiaohongshuTools } from "./tools/xiaohongshu.js";
|
|
8
7
|
import { registerTiktokTools } from "./tools/tiktok.js";
|
|
9
8
|
import { registerInstagramTools } from "./tools/instagram.js";
|
|
10
9
|
import { registerYoutubeTools } from "./tools/youtube.js";
|
|
11
10
|
import { registerDcardTools } from "./tools/dcard.js";
|
|
12
11
|
import { registerSystemTools } from "./tools/system.js";
|
|
13
12
|
import { registerJob104Tools } from "./tools/job104.js";
|
|
13
|
+
import { registerGmapsTools } from "./tools/gmaps.js";
|
|
14
14
|
export function createServer() {
|
|
15
15
|
const server = new McpServer({
|
|
16
16
|
name: "bycrawl",
|
|
@@ -21,12 +21,12 @@ export function createServer() {
|
|
|
21
21
|
registerFacebookTools(server);
|
|
22
22
|
registerRedditTools(server);
|
|
23
23
|
registerLinkedinTools(server);
|
|
24
|
-
registerXiaohongshuTools(server);
|
|
25
24
|
registerTiktokTools(server);
|
|
26
25
|
registerInstagramTools(server);
|
|
27
26
|
registerYoutubeTools(server);
|
|
28
27
|
registerDcardTools(server);
|
|
29
28
|
registerJob104Tools(server);
|
|
29
|
+
registerGmapsTools(server);
|
|
30
30
|
registerSystemTools(server);
|
|
31
31
|
return server;
|
|
32
32
|
}
|
package/build/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,cAAc,CAAC,MAAM,CAAC,CAAC;IACvB,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE5B,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/build/tools/dcard.js
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { bycrawlGet } from "../client.js";
|
|
3
3
|
export function registerDcardTools(server) {
|
|
4
|
-
server.tool("
|
|
5
|
-
server.tool("dcard_get_post_comments", "Get comments for a Dcard post", {
|
|
6
|
-
postId: z.string().describe("Dcard post ID"),
|
|
7
|
-
sort: z
|
|
8
|
-
.enum(["oldest", "newest", "popular"])
|
|
9
|
-
.optional()
|
|
10
|
-
.describe("Sort order"),
|
|
11
|
-
}, async ({ postId, sort }) => bycrawlGet(`/dcard/posts/${postId}/comments`, { sort }));
|
|
12
|
-
server.tool("dcard_get_forum", "Get Dcard forum info by alias", { alias: z.string().describe("Forum alias (e.g. trending, talk, mood)") }, async ({ alias }) => bycrawlGet(`/dcard/forums/${alias}`));
|
|
4
|
+
server.tool("dcard_get_forum", "Get Dcard forum info by alias", { alias: z.string().describe("Forum alias (e.g. trending, talk, mood)") }, { readOnlyHint: true }, async ({ alias }) => bycrawlGet(`/dcard/forums/${alias}`));
|
|
13
5
|
server.tool("dcard_get_forum_posts", "Get posts from a Dcard forum", {
|
|
14
6
|
alias: z.string().describe("Forum alias (e.g. trending, talk, mood)"),
|
|
15
|
-
|
|
7
|
+
count: z.number().optional().describe("Number of posts (1-30)"),
|
|
16
8
|
popular: z.boolean().optional().describe("Show popular posts only"),
|
|
17
|
-
}, async ({ alias,
|
|
9
|
+
}, { readOnlyHint: true }, async ({ alias, count, popular }) => bycrawlGet(`/dcard/forums/${alias}/posts`, { count, popular }));
|
|
18
10
|
server.tool("dcard_search_posts", "Search Dcard posts by keyword", {
|
|
19
11
|
q: z.string().describe("Search query"),
|
|
20
|
-
|
|
12
|
+
count: z.number().optional().describe("Number of results (1-100)"),
|
|
21
13
|
offset: z.number().optional().describe("Offset for pagination"),
|
|
22
|
-
}, async ({ q,
|
|
23
|
-
server.tool("dcard_get_persona", "Get a Dcard persona by
|
|
14
|
+
}, { readOnlyHint: true }, async ({ q, count, offset }) => bycrawlGet("/dcard/posts/search", { q, count, offset }));
|
|
15
|
+
server.tool("dcard_get_persona", "Get a Dcard persona by username", { username: z.string().describe("Dcard username") }, { readOnlyHint: true }, async ({ username }) => bycrawlGet(`/dcard/personas/${username}`));
|
|
24
16
|
}
|
|
25
17
|
//# sourceMappingURL=dcard.js.map
|