@quidli/connect-mcp 0.1.1 → 0.3.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 CHANGED
@@ -1,57 +1,128 @@
1
- # @quidli/connect-mcp
1
+ # Quidli Connect MCP
2
2
 
3
- MCP server exposing [Quidli Connect](https://connect.quid.li) product APIs as tools for Cursor, Claude Desktop, and other MCP hosts.
3
+ Use [Quidli Connect](https://connect.quid.li) from Cursor, Claude Desktop, or any MCP-compatible client — wallet lookup, scores, Smart Send drops, and the Connect agent.
4
4
 
5
- ## Requirements
5
+ ## Choose how to connect
6
6
 
7
- - Node.js 20+
8
- - A Connect API key ([Enable API access](https://connect.quid.li))
9
7
 
10
- ## Cursor configuration
8
+ | | **Hosted** (`mcp.connect.quid.li`) | **Local** (`npx @quidli/connect-mcp`) |
9
+ | --------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
10
+ | **Setup** | Add a URL in MCP settings | Run a local process via `npx` |
11
+ | **Auth** | Connect API key only | API key, x402 pay-per-call, or none for lookup/scores/agent |
12
+ | **Pros** | No install; always on Quidli infrastructure; simplest setup | Wallet private key never leaves your machine; pay per call with USDC instead of an API key |
13
+ | **Cons** | Requires a Connect API key; no pay-per-call billing | Requires Node.js 20+; `npx` may download the package on first run |
14
+ | **Limitations** | Cannot use x402 / wallet auth — do **not** send a private key to the hosted endpoint | x402 mode needs USDC on Base (mainnet `8453`); `connect_drop` requires an API key |
11
15
 
12
- ### Remote URL (recommended for production)
13
16
 
14
- Same pattern as GitHub MCP — Cursor connects to a hosted endpoint and sends your API key in headers:
17
+ Get a Connect API key at [connect.quid.li](https://connect.quid.li) → **Enable API access**.
18
+
19
+ ---
20
+
21
+ ## Hosted (recommended)
22
+
23
+ Best if you have a Connect API key and want zero local setup.
24
+
25
+ ### Cursor
26
+
27
+ **If you already have other MCP servers:** skip the deeplink and [add manually](#cursor-manual) — the one-click install can overwrite your entire `mcp.json` in some Cursor versions.
28
+
29
+ [Add to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=quidli-connect&config=eyJ1cmwiOiJodHRwczovL21jcC5jb25uZWN0LnF1aWQubGkiLCJoZWFkZXJzIjp7IngtYXBpLWtleSI6IiJ9fQ==)
30
+
31
+ Or paste into **Cursor Settings → MCP → Add new MCP server**:
32
+
33
+ ```
34
+ cursor://anysphere.cursor-deeplink/mcp/install?name=quidli-connect&config=eyJ1cmwiOiJodHRwczovL21jcC5jb25uZWN0LnF1aWQubGkiLCJoZWFkZXJzIjp7IngtYXBpLWtleSI6IiJ9fQ==
35
+ ```
36
+
37
+ Then open **Cursor Settings → MCP → quidli-connect** and set `headers.x-api-key` to your Connect API key.
38
+
39
+ #### Add manually {#cursor-manual}
40
+
41
+ Merge this into `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project) under `mcpServers` — do not replace your existing entries:
42
+
43
+ ```json
44
+ "quidli-connect": {
45
+ "url": "https://mcp.connect.quid.li",
46
+ "headers": {
47
+ "x-api-key": "<your-connect-api-key>"
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### Claude Desktop
53
+
54
+ 1. Open **Claude → Settings → Connectors** (or **Settings → Developer → Edit Config** on older versions).
55
+ 2. Add a custom MCP connector with URL `https://mcp.connect.quid.li` and header `x-api-key: <your-connect-api-key>`.
56
+
57
+ Or merge this into your config file and restart Claude:
58
+
59
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
60
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
15
61
 
16
62
  ```json
17
63
  {
18
64
  "mcpServers": {
19
- "connect": {
65
+ "quidli-connect": {
20
66
  "url": "https://mcp.connect.quid.li",
21
67
  "headers": {
22
- "x-api-key": "set-your-api-key-here"
68
+ "x-api-key": "<your-connect-api-key>"
23
69
  }
24
70
  }
25
71
  }
26
72
  }
27
73
  ```
28
74
 
29
- After one-click install, open **Cursor Settings → MCP → connect** and replace `set-your-api-key-here` with your real API key from [connect.quid.li](https://connect.quid.li) (Enable API access). Until you do, the server returns `401` with setup instructions.
30
-
31
- **Add to Cursor** deeplink (placeholder key pre-filled):
75
+ If your Claude version does not support remote `url` connectors, use the local bridge instead (still hosted API, runs a small local helper):
32
76
 
33
- ```
34
- cursor://anysphere.cursor-deeplink/mcp/install?name=connect&config=eyJ1cmwiOiJodHRwczovL21jcC5jb25uZWN0LnF1aWQubGkIiwiaGVhZGVycyI6eyJ4LWFwaS1rZXkiOiJzZXQteW91ci1hcGkta2V5LWhlcmUifX0=
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "quidli-connect": {
81
+ "command": "npx",
82
+ "args": [
83
+ "-y",
84
+ "mcp-remote@latest",
85
+ "--http",
86
+ "https://mcp.connect.quid.li",
87
+ "--header",
88
+ "x-api-key:${CONNECT_API_KEY}"
89
+ ],
90
+ "env": {
91
+ "CONNECT_API_KEY": "<your-connect-api-key>"
92
+ }
93
+ }
94
+ }
95
+ }
35
96
  ```
36
97
 
37
- Or generate programmatically:
98
+ Requires **Node.js 20+** for the bridge. Restart Claude Desktop after saving. You should see a hammer icon in the chat input when tools are available.
38
99
 
39
- ```typescript
40
- import { buildCursorMcpInstallDeeplink } from '@quidli/connect-mcp/deeplink';
41
- ```
100
+ Until a valid API key is set, requests return `401` with setup instructions.
42
101
 
43
- ### Local stdio (npm / development)
102
+ ---
44
103
 
45
- Add to `.cursor/mcp.json` (or global MCP settings):
104
+ ## Local (stdio)
105
+
106
+ Best if you want pay-per-call with a wallet, or prefer credentials in a local env file.
107
+
108
+ Requires **Node.js 20+**.
109
+
110
+ ### Cursor
111
+
112
+ Add one of the following to `.cursor/mcp.json` (project) or your global MCP settings.
113
+
114
+ #### Option A — API key
115
+
116
+ Same billing model as hosted; credentials stay on your machine.
46
117
 
47
118
  ```json
48
119
  {
49
120
  "mcpServers": {
50
- "connect": {
121
+ "quidli-connect": {
51
122
  "command": "npx",
52
123
  "args": ["-y", "@quidli/connect-mcp"],
53
124
  "env": {
54
- "CONNECT_API_KEY": "<your-api-key>",
125
+ "CONNECT_API_KEY": "<your-connect-api-key>",
55
126
  "CONNECT_API_BASE_URL": "https://api.connect.quid.li"
56
127
  }
57
128
  }
@@ -59,59 +130,114 @@ Add to `.cursor/mcp.json` (or global MCP settings):
59
130
  }
60
131
  ```
61
132
 
62
- `CONNECT_API_KEY` is **required** for stdio mode — the process exits at startup if missing. Remote HTTP mode reads `x-api-key` from each request instead.
133
+ #### Option B — x402 pay-per-call (wallet)
63
134
 
64
- ## Standalone HTTP server (local / debugging)
135
+ No API key. Authenticated calls pay automatically when the API returns HTTP 402. Your wallet private key is only read by the local process.
65
136
 
66
- ```bash
67
- cd packages/connect-mcp
68
- npm run build
69
- CONNECT_API_BASE_URL=https://api.connect.quid.li npm run start:http
137
+ ```json
138
+ {
139
+ "mcpServers": {
140
+ "quidli-connect": {
141
+ "command": "npx",
142
+ "args": ["-y", "@quidli/connect-mcp"],
143
+ "env": {
144
+ "EVM_PRIVATE_KEY": "0x<wallet-with-usdc-on-base>",
145
+ "CONNECT_API_BASE_URL": "https://api.connect.quid.li",
146
+ "CONNECT_X402_EVM_NETWORK": "8453"
147
+ }
148
+ }
149
+ }
150
+ }
70
151
  ```
71
152
 
72
- Environment:
153
+ Use `84532` for Base Sepolia when pointing at a staging API.
73
154
 
74
- | Variable | Default | Description |
75
- |----------|---------|-------------|
76
- | `CONNECT_MCP_HTTP_HOST` | `0.0.0.0` | Bind address |
77
- | `CONNECT_MCP_HTTP_PORT` | `8080` | Listen port |
78
- | `CONNECT_API_BASE_URL` | `https://api.connect.quid.li` | Upstream Connect API |
155
+ #### Option C — no credentials (lookup / scores / agent)
79
156
 
80
- Health check: `GET /health`
157
+ The MCP process starts without `CONNECT_API_KEY` or `EVM_PRIVATE_KEY`. `connect_lookup`, `connect_scores_*`, `connect_agent_prompt`, and `connect_get_price` call the API without auth. That succeeds when x402 is disabled (price `0`, typical for local). On production with x402 enabled, those tools return HTTP 402 until you set a key or wallet.
81
158
 
82
- ## Tools (7)
159
+ `connect_me`, `connect_drop`, and `connect_drop_balance` still require `CONNECT_API_KEY`.
83
160
 
84
- | Tool | HTTP | Auth |
85
- |------|------|------|
86
- | `connect_get_price` | `GET /price` | none |
87
- | `connect_lookup` | `POST /lookup` | api-key |
88
- | `connect_scores_batch` | `POST /scores` | api-key |
89
- | `connect_scores_by_account` | `GET /scores/:platform/:identifier` | api-key |
90
- | `connect_scores_by_username` | `GET /scores/u/:username` | api-key |
91
- | `connect_drop` | `POST /drop` | api-key |
92
- | `connect_agent_prompt` | `POST /agent` | api-key |
161
+ ```json
162
+ {
163
+ "mcpServers": {
164
+ "quidli-connect": {
165
+ "command": "npx",
166
+ "args": ["-y", "@quidli/connect-mcp"],
167
+ "env": {
168
+ "CONNECT_API_BASE_URL": "http://127.0.0.1:3011"
169
+ }
170
+ }
171
+ }
172
+ }
173
+ ```
174
+
175
+ ### Claude Desktop
93
176
 
94
- Not exposed: `POST /lookup/exposed`, agent feedback, member/admin routes.
177
+ 1. Open **Claude → Settings → Developer → Edit Config** (enable Developer mode first if you do not see it).
178
+ 2. Merge one of the blocks below into `mcpServers` in:
95
179
 
96
- ## Auth notes
180
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
181
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
97
182
 
98
- - **MCP uses api-key only.** x402 pay-per-call and Privy Bearer are HTTP-only (use [API docs](https://connect.quid.li/docs) or skills directly).
99
- - `connect_get_price` is public on the HTTP API and does not send `x-api-key`.
183
+ 3. Restart Claude Desktop.
100
184
 
101
- ## Development
185
+ #### Option A — API key
102
186
 
103
- ```bash
104
- cd packages/connect-mcp
105
- npm install
106
- npm test
107
- npm run build
108
- CONNECT_API_KEY=<key> npm start
187
+ ```json
188
+ {
189
+ "mcpServers": {
190
+ "quidli-connect": {
191
+ "command": "npx",
192
+ "args": ["-y", "@quidli/connect-mcp"],
193
+ "env": {
194
+ "CONNECT_API_KEY": "<your-connect-api-key>",
195
+ "CONNECT_API_BASE_URL": "https://api.connect.quid.li"
196
+ }
197
+ }
198
+ }
199
+ }
109
200
  ```
110
201
 
111
- ## Publish
202
+ #### Option B — x402 pay-per-call (wallet)
112
203
 
113
- ```bash
114
- npm publish --access public
204
+ ```json
205
+ {
206
+ "mcpServers": {
207
+ "quidli-connect": {
208
+ "command": "npx",
209
+ "args": ["-y", "@quidli/connect-mcp"],
210
+ "env": {
211
+ "EVM_PRIVATE_KEY": "0x<wallet-with-usdc-on-base>",
212
+ "CONNECT_API_BASE_URL": "https://api.connect.quid.li",
213
+ "CONNECT_X402_EVM_NETWORK": "8453"
214
+ }
215
+ }
216
+ }
217
+ }
115
218
  ```
116
219
 
117
- (Requires `@quidli` npm org access.)
220
+ If both `CONNECT_API_KEY` and `EVM_PRIVATE_KEY` are set, the API key is used.
221
+
222
+ Credentials are optional in local mode. Without either variable, lookup/scores/agent still work when the API does not charge x402. `connect_me` and Smart Send tools require `CONNECT_API_KEY`.
223
+
224
+ ---
225
+
226
+ ## Tools
227
+
228
+
229
+ | Tool | What it does |
230
+ | ---------------------------- | --------------------------------------------------------- |
231
+ | `connect_get_price` | List reference prices (no auth) |
232
+ | `connect_lookup` | Resolve social identities to EVM and SOL wallet addresses |
233
+ | `connect_lookup_exposed` | List platforms a recipient has exposed on Connect |
234
+ | `connect_scores_batch` | Batch scores for accounts or usernames |
235
+ | `connect_scores_by_account` | Scores for one linked account |
236
+ | `connect_scores_by_username` | Scores by Connect username |
237
+ | `connect_me` | API key owner profile, scores, and linked accounts — **API key only** |
238
+ | `connect_drop` | Smart Send (batch token transfer) — **API key only** |
239
+ | `connect_drop_balance` | Smart Send wallet balances on a chain — **API key only** |
240
+ | `connect_agent_prompt` | Natural-language agent for recipients discovery |
241
+
242
+
243
+ Ask your client to use these tools when you need Connect data or actions.
package/dist/client.d.ts CHANGED
@@ -1,16 +1,22 @@
1
1
  import type { ConnectClientConfig } from './config.js';
2
2
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
3
  export type HttpMethod = 'GET' | 'POST';
4
+ export type ConnectAuthMode = 'api-key' | 'x402';
4
5
  export interface ConnectRequestOptions {
5
6
  method: HttpMethod;
6
7
  path: string;
7
8
  authenticated?: boolean;
9
+ /** Fail before calling the API when CONNECT_API_KEY is unset (Smart Send /me). */
10
+ requireApiKey?: boolean;
8
11
  body?: unknown;
9
12
  query?: Record<string, string | undefined>;
10
13
  timeoutMs?: number;
11
14
  }
12
15
  export declare class ConnectClient {
13
16
  private readonly config;
17
+ private readonly authMode;
18
+ private readonly paidFetch?;
14
19
  constructor(config: ConnectClientConfig);
20
+ private resolveFetch;
15
21
  request(options: ConnectRequestOptions): Promise<CallToolResult>;
16
22
  }
package/dist/client.js CHANGED
@@ -1,13 +1,29 @@
1
1
  import { mapHttpError } from './errors.js';
2
2
  import { errorResult, successResult } from './result.js';
3
+ import { createPaidFetch } from './x402-fetch.js';
3
4
  export class ConnectClient {
4
5
  config;
6
+ authMode;
7
+ paidFetch;
5
8
  constructor(config) {
6
9
  this.config = config;
10
+ this.authMode = config.evmPrivateKey && !config.apiKey ? 'x402' : 'api-key';
11
+ if (!config.apiKey && config.evmPrivateKey) {
12
+ this.paidFetch = createPaidFetch(config.evmPrivateKey, config.x402EvmNetwork);
13
+ }
14
+ }
15
+ resolveFetch(authenticated) {
16
+ if (authenticated && this.paidFetch) {
17
+ return this.paidFetch;
18
+ }
19
+ return fetch;
7
20
  }
8
21
  async request(options) {
9
22
  const { method, path, body, query, timeoutMs } = options;
10
23
  const authenticated = options.authenticated ?? true;
24
+ if (options.requireApiKey && !this.config.apiKey) {
25
+ return errorResult('This tool requires CONNECT_API_KEY. Get a key at https://connect.quid.li (Enable API access). x402 wallet auth is not supported for this action.');
26
+ }
11
27
  const url = new URL(path, `${this.config.baseUrl}/`);
12
28
  if (query) {
13
29
  for (const [key, value] of Object.entries(query)) {
@@ -19,7 +35,7 @@ export class ConnectClient {
19
35
  const headers = {
20
36
  accept: 'application/json',
21
37
  };
22
- if (authenticated) {
38
+ if (authenticated && this.config.apiKey) {
23
39
  headers['x-api-key'] = this.config.apiKey;
24
40
  }
25
41
  if (body !== undefined) {
@@ -29,8 +45,9 @@ export class ConnectClient {
29
45
  const timeout = timeoutMs
30
46
  ? setTimeout(() => controller.abort(), timeoutMs)
31
47
  : undefined;
48
+ const httpFetch = this.resolveFetch(authenticated);
32
49
  try {
33
- const response = await fetch(url, {
50
+ const response = await httpFetch(url, {
34
51
  method,
35
52
  headers,
36
53
  body: body !== undefined ? JSON.stringify(body) : undefined,
@@ -39,9 +56,9 @@ export class ConnectClient {
39
56
  const raw = await response.json().catch(() => ({}));
40
57
  if (!response.ok) {
41
58
  if (response.status === 504) {
42
- return errorResult(mapHttpError(504, raw));
59
+ return errorResult(mapHttpError(504, raw, this.authMode));
43
60
  }
44
- return errorResult(mapHttpError(response.status, raw));
61
+ return errorResult(mapHttpError(response.status, raw, this.authMode));
45
62
  }
46
63
  return successResult({
47
64
  httpStatus: response.status,
package/dist/config.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  export interface ConnectClientConfig {
2
2
  baseUrl: string;
3
- apiKey: string;
3
+ /** When set, sent on every authenticated Connect API request. */
4
+ apiKey?: string;
5
+ /** When apiKey is unset, authenticated requests pay via x402 on 402. */
6
+ evmPrivateKey?: `0x${string}`;
7
+ /** EVM chain id for x402 (default 8453 = Base mainnet). */
8
+ x402EvmNetwork: number;
4
9
  }
5
10
  export interface HttpServerConfig {
6
11
  host: string;
@@ -8,4 +13,11 @@ export interface HttpServerConfig {
8
13
  baseUrl: string;
9
14
  }
10
15
  export declare function loadHttpServerConfig(env?: NodeJS.ProcessEnv): HttpServerConfig;
16
+ /**
17
+ * Stdio MCP: credentials are optional.
18
+ * Lookup/scores/agent work without auth when the API allows it (x402 price 0)
19
+ * or with CONNECT_API_KEY / EVM_PRIVATE_KEY. /account/me and /drop require an API key.
20
+ */
11
21
  export declare function loadConfig(env?: NodeJS.ProcessEnv): ConnectClientConfig;
22
+ /** Hosted HTTP MCP: api-key from request header only. */
23
+ export declare function createHostedClientConfig(apiKey: string, baseUrl: string): ConnectClientConfig;
package/dist/config.js CHANGED
@@ -1,6 +1,19 @@
1
+ import { normalizeEvmPrivateKey } from './x402-fetch.js';
1
2
  const DEFAULT_BASE_URL = 'https://api.connect.quid.li';
2
3
  const DEFAULT_HTTP_HOST = '0.0.0.0';
3
4
  const DEFAULT_HTTP_PORT = 8080;
5
+ const DEFAULT_X402_EVM_NETWORK = 8453;
6
+ function parseX402EvmNetwork(env) {
7
+ const raw = env.CONNECT_X402_EVM_NETWORK?.trim() || env.X402_EVM_NETWORK?.trim() || String(DEFAULT_X402_EVM_NETWORK);
8
+ const network = Number(raw);
9
+ if (!Number.isInteger(network) || network < 1) {
10
+ throw new Error(`CONNECT_X402_EVM_NETWORK must be a positive integer (got "${raw}")`);
11
+ }
12
+ return network;
13
+ }
14
+ function loadBaseUrl(env) {
15
+ return (env.CONNECT_API_BASE_URL?.trim() || DEFAULT_BASE_URL).replace(/\/$/, '');
16
+ }
4
17
  export function loadHttpServerConfig(env = process.env) {
5
18
  const host = env.CONNECT_MCP_HTTP_HOST?.trim() || DEFAULT_HTTP_HOST;
6
19
  const portRaw = env.CONNECT_MCP_HTTP_PORT?.trim() || String(DEFAULT_HTTP_PORT);
@@ -8,14 +21,29 @@ export function loadHttpServerConfig(env = process.env) {
8
21
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
9
22
  throw new Error(`CONNECT_MCP_HTTP_PORT must be a valid port (got "${portRaw}")`);
10
23
  }
11
- const baseUrl = (env.CONNECT_API_BASE_URL?.trim() || DEFAULT_BASE_URL).replace(/\/$/, '');
12
- return { host, port, baseUrl };
24
+ return { host, port, baseUrl: loadBaseUrl(env) };
13
25
  }
26
+ /**
27
+ * Stdio MCP: credentials are optional.
28
+ * Lookup/scores/agent work without auth when the API allows it (x402 price 0)
29
+ * or with CONNECT_API_KEY / EVM_PRIVATE_KEY. /account/me and /drop require an API key.
30
+ */
14
31
  export function loadConfig(env = process.env) {
15
- const apiKey = env.CONNECT_API_KEY?.trim();
16
- if (!apiKey) {
17
- throw new Error('CONNECT_API_KEY is required. Get a key at https://connect.quid.li (Enable API access).');
18
- }
19
- const baseUrl = (env.CONNECT_API_BASE_URL?.trim() || DEFAULT_BASE_URL).replace(/\/$/, '');
20
- return { apiKey, baseUrl };
32
+ const apiKey = env.CONNECT_API_KEY?.trim() || undefined;
33
+ const evmPrivateKey = normalizeEvmPrivateKey(env.EVM_PRIVATE_KEY);
34
+ const x402EvmNetwork = parseX402EvmNetwork(env);
35
+ return {
36
+ baseUrl: loadBaseUrl(env),
37
+ apiKey,
38
+ evmPrivateKey: apiKey ? undefined : evmPrivateKey,
39
+ x402EvmNetwork,
40
+ };
41
+ }
42
+ /** Hosted HTTP MCP: api-key from request header only. */
43
+ export function createHostedClientConfig(apiKey, baseUrl) {
44
+ return {
45
+ baseUrl,
46
+ apiKey,
47
+ x402EvmNetwork: DEFAULT_X402_EVM_NETWORK,
48
+ };
21
49
  }
package/dist/deeplink.js CHANGED
@@ -7,7 +7,7 @@ export function buildCursorRemoteMcpConfig(apiKey = CONNECT_MCP_API_KEY_PLACEHOL
7
7
  },
8
8
  };
9
9
  }
10
- export function buildCursorMcpInstallDeeplink(serverName = 'connect', config = buildCursorRemoteMcpConfig()) {
10
+ export function buildCursorMcpInstallDeeplink(serverName = 'quidli-connect', config = buildCursorRemoteMcpConfig()) {
11
11
  const encoded = Buffer.from(JSON.stringify(config), 'utf8').toString('base64');
12
12
  return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(serverName)}&config=${encoded}`;
13
13
  }
package/dist/errors.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export declare function parseNestErrorMessage(body: unknown): string;
2
- export declare function mapHttpError(status: number, body: unknown): string;
2
+ export type ConnectAuthMode = 'api-key' | 'x402';
3
+ export declare function mapHttpError(status: number, body: unknown, authMode?: ConnectAuthMode): string;
package/dist/errors.js CHANGED
@@ -10,15 +10,19 @@ export function parseNestErrorMessage(body) {
10
10
  }
11
11
  return 'Request failed';
12
12
  }
13
- export function mapHttpError(status, body) {
13
+ export function mapHttpError(status, body, authMode = 'api-key') {
14
14
  const detail = parseNestErrorMessage(body);
15
15
  switch (status) {
16
16
  case 400:
17
17
  return detail;
18
18
  case 401:
19
- return `Unauthorized: ${detail}. Verify CONNECT_API_KEY at https://connect.quid.li`;
19
+ return authMode === 'api-key'
20
+ ? `Unauthorized: ${detail}. Verify CONNECT_API_KEY at https://connect.quid.li`
21
+ : `Unauthorized: ${detail}. Verify EVM_PRIVATE_KEY and wallet USDC balance for x402.`;
20
22
  case 402:
21
- return `Payment required (${detail}). MCP requires a valid Connect API key — x402 is not supported in MCP.`;
23
+ return authMode === 'api-key'
24
+ ? `Payment required (${detail}). Set CONNECT_API_KEY or use local stdio with EVM_PRIVATE_KEY for x402.`
25
+ : `Payment failed (${detail}). Ensure your wallet has USDC on the expected network and EVM_PRIVATE_KEY is correct.`;
22
26
  case 404:
23
27
  return detail;
24
28
  case 504:
File without changes
package/dist/index.js CHANGED
File without changes
package/dist/mount.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
2
+ import { createHostedClientConfig } from './config.js';
2
3
  import { createMcpServer } from './create-mcp-server.js';
3
4
  import { validateRequestApiKey } from './http-auth.js';
4
5
  export { resolveRequestHost } from './request-host.js';
@@ -49,7 +50,7 @@ export function mountConnectMcpHttp(router, options) {
49
50
  return;
50
51
  }
51
52
  try {
52
- const server = createMcpServer({ apiKey: auth.apiKey, baseUrl });
53
+ const server = createMcpServer(createHostedClientConfig(auth.apiKey, baseUrl));
53
54
  const transport = new StreamableHTTPServerTransport({
54
55
  sessionIdGenerator: undefined, // stateless — no session tracking
55
56
  enableJsonResponse: true,
package/dist/schemas.d.ts CHANGED
@@ -28,6 +28,21 @@ export declare const lookupInputSchema: {
28
28
  username: z.ZodOptional<z.ZodString>;
29
29
  }, z.ZodTypeAny, "passthrough">>, "many">;
30
30
  };
31
+ export declare const lookupExposedInputSchema: {
32
+ recipient: z.ZodObject<{
33
+ type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
34
+ id: z.ZodOptional<z.ZodString>;
35
+ username: z.ZodOptional<z.ZodString>;
36
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
37
+ type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
38
+ id: z.ZodOptional<z.ZodString>;
39
+ username: z.ZodOptional<z.ZodString>;
40
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
41
+ type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
42
+ id: z.ZodOptional<z.ZodString>;
43
+ username: z.ZodOptional<z.ZodString>;
44
+ }, z.ZodTypeAny, "passthrough">>;
45
+ };
31
46
  export declare const scoresBatchInputSchema: {
32
47
  users: z.ZodArray<z.ZodObject<{
33
48
  type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
@@ -60,6 +75,9 @@ export declare const scoresByAccountInputSchema: {
60
75
  export declare const scoresByUsernameInputSchema: {
61
76
  username: z.ZodString;
62
77
  };
78
+ export declare const dropBalanceInputSchema: {
79
+ chainId: z.ZodNumber;
80
+ };
63
81
  export declare const dropInputSchema: {
64
82
  idempotencyKey: z.ZodString;
65
83
  chainId: z.ZodNumber;
package/dist/schemas.js CHANGED
@@ -26,6 +26,9 @@ export const lookupInputSchema = {
26
26
  .min(1)
27
27
  .describe('Recipients to resolve to EVM/Solana wallets'),
28
28
  };
29
+ export const lookupExposedInputSchema = {
30
+ recipient: linkedAccountSchema.describe('Recipient whose exposed linked accounts and Connect profile should be listed'),
31
+ };
29
32
  export const scoresBatchInputSchema = {
30
33
  users: z.array(linkedAccountSchema).min(1).describe('Users to fetch scores for'),
31
34
  filter: z
@@ -59,6 +62,9 @@ export const scoresByAccountInputSchema = {
59
62
  export const scoresByUsernameInputSchema = {
60
63
  username: z.string().min(1),
61
64
  };
65
+ export const dropBalanceInputSchema = {
66
+ chainId: z.number().int().describe('EVM chain ID (Smart Send supported chains only)'),
67
+ };
62
68
  export const dropInputSchema = {
63
69
  idempotencyKey: z.string().uuid(),
64
70
  chainId: z.number().int(),
@@ -1,4 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { ConnectClient } from '../client.js';
3
- export declare const CONNECT_MCP_TOOL_NAMES: readonly ["connect_get_price", "connect_lookup", "connect_scores_batch", "connect_scores_by_account", "connect_scores_by_username", "connect_drop", "connect_agent_prompt"];
3
+ export declare const CONNECT_MCP_TOOL_NAMES: readonly ["connect_get_price", "connect_lookup", "connect_lookup_exposed", "connect_scores_batch", "connect_scores_by_account", "connect_scores_by_username", "connect_me", "connect_drop", "connect_drop_balance", "connect_agent_prompt"];
4
4
  export declare function registerTools(server: McpServer, client: ConnectClient): void;
@@ -1,12 +1,15 @@
1
- import { agentPromptInputSchema, dropInputSchema, lookupInputSchema, scoresBatchInputSchema, scoresByAccountInputSchema, scoresByUsernameInputSchema, } from '../schemas.js';
1
+ import { agentPromptInputSchema, dropBalanceInputSchema, dropInputSchema, lookupExposedInputSchema, lookupInputSchema, scoresBatchInputSchema, scoresByAccountInputSchema, scoresByUsernameInputSchema, } from '../schemas.js';
2
2
  const AGENT_TIMEOUT_MS = 65_000;
3
3
  export const CONNECT_MCP_TOOL_NAMES = [
4
4
  'connect_get_price',
5
5
  'connect_lookup',
6
+ 'connect_lookup_exposed',
6
7
  'connect_scores_batch',
7
8
  'connect_scores_by_account',
8
9
  'connect_scores_by_username',
10
+ 'connect_me',
9
11
  'connect_drop',
12
+ 'connect_drop_balance',
10
13
  'connect_agent_prompt',
11
14
  ];
12
15
  export function registerTools(server, client) {
@@ -16,6 +19,11 @@ export function registerTools(server, client) {
16
19
  path: '/lookup',
17
20
  body: { recipients },
18
21
  }));
22
+ server.tool('connect_lookup_exposed', 'List platforms a recipient has exposed on Connect, with enriched profile, scores, and wallet addresses. May require x402 payment when the profile owner charges for lookups.', lookupExposedInputSchema, async ({ recipient }) => client.request({
23
+ method: 'POST',
24
+ path: '/lookup/exposed',
25
+ body: { recipient },
26
+ }));
19
27
  server.tool('connect_scores_batch', 'Batch scores for linked accounts or Connect usernames. Optional filter excludes users below minScore.', scoresBatchInputSchema, async ({ users, filter }) => client.request({
20
28
  method: 'POST',
21
29
  path: '/scores',
@@ -29,14 +37,25 @@ export function registerTools(server, client) {
29
37
  method: 'GET',
30
38
  path: `/scores/u/${encodeURIComponent(username.trim())}`,
31
39
  }));
40
+ server.tool('connect_me', 'Get the Connect profile, scores, and all linked accounts for the API key owner. Use to identify which user the key belongs to.', {}, async () => client.request({
41
+ method: 'GET',
42
+ path: '/account/me',
43
+ }));
32
44
  server.tool('connect_drop', 'Execute a Smart Send (batch token transfer). Returns 201 when submitted or 202 when recipients still processing — retry with same idempotencyKey.', dropInputSchema, async ({ ignoreFailedRecipients, ...body }) => client.request({
33
45
  method: 'POST',
34
46
  path: '/drop',
35
47
  body,
48
+ requireApiKey: true,
36
49
  query: ignoreFailedRecipients === true
37
50
  ? { ignoreFailedRecipients: 'true' }
38
51
  : undefined,
39
52
  }));
53
+ server.tool('connect_drop_balance', 'Get native and ERC-20 balances for the API key owner Smart Send embedded wallet on a chain. Use before connect_drop to verify gas and token funds.', dropBalanceInputSchema, async ({ chainId }) => client.request({
54
+ method: 'GET',
55
+ path: '/drop/balance',
56
+ requireApiKey: true,
57
+ query: { chainId: String(chainId) },
58
+ }));
40
59
  server.tool('connect_agent_prompt', 'Natural-language agent turn for Farcaster/Lens cohort discovery. Blocking up to 60s. Omit sessionId to start; include to continue. Do not send requester.', agentPromptInputSchema, async ({ prompt, sessionId }) => client.request({
41
60
  method: 'POST',
42
61
  path: '/agent',
@@ -0,0 +1,2 @@
1
+ export declare function normalizeEvmPrivateKey(value: string | undefined): `0x${string}` | undefined;
2
+ export declare function createPaidFetch(evmPrivateKey: `0x${string}`, chainId: number): typeof fetch;
@@ -0,0 +1,30 @@
1
+ import { x402Client, wrapFetchWithPayment } from '@x402/fetch';
2
+ import { ExactEvmScheme } from '@x402/evm/exact/client';
3
+ import { toClientEvmSigner } from '@x402/evm';
4
+ import { privateKeyToAccount } from 'viem/accounts';
5
+ import { createPublicClient, http } from 'viem';
6
+ import { base, baseSepolia } from 'viem/chains';
7
+ const EVM_CHAIN_BY_ID = {
8
+ 8453: base,
9
+ 84532: baseSepolia,
10
+ };
11
+ export function normalizeEvmPrivateKey(value) {
12
+ const trimmed = value?.trim();
13
+ if (!trimmed) {
14
+ return undefined;
15
+ }
16
+ return (trimmed.startsWith('0x') ? trimmed : `0x${trimmed}`);
17
+ }
18
+ function getEvmPublicClient(chainId) {
19
+ const chain = EVM_CHAIN_BY_ID[chainId] ?? base;
20
+ return createPublicClient({ chain, transport: http() });
21
+ }
22
+ export function createPaidFetch(evmPrivateKey, chainId) {
23
+ const account = privateKeyToAccount(evmPrivateKey);
24
+ const evmSigner = toClientEvmSigner(account, getEvmPublicClient(chainId));
25
+ const client = new x402Client();
26
+ const scheme = new ExactEvmScheme(evmSigner);
27
+ client.register(`eip155:${chainId}`, scheme);
28
+ client.register('eip155:*', scheme);
29
+ return wrapFetchWithPayment(fetch, client);
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quidli/connect-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for Quidli Connect product APIs",
5
5
  "keywords": [
6
6
  "mcp",
@@ -13,6 +13,11 @@
13
13
  "agent"
14
14
  ],
15
15
  "homepage": "https://connect.quid.li",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/Quidli/sns-backend.git",
19
+ "directory": "packages/connect-mcp"
20
+ },
16
21
  "author": {
17
22
  "name": "Quidli",
18
23
  "url": "https://quid.xyz"
@@ -34,8 +39,8 @@
34
39
  "./http": "./dist/http.js"
35
40
  },
36
41
  "scripts": {
37
- "build": "tsc && chmod +x dist/index.js dist/index-http.js",
38
- "prepublishOnly": "npm run build",
42
+ "build": "tsc",
43
+ "prepublishOnly": "npm run build && chmod +x dist/index.js dist/index-http.js",
39
44
  "start": "node dist/index.js",
40
45
  "start:http": "node dist/index-http.js",
41
46
  "test": "vitest run",
@@ -47,10 +52,14 @@
47
52
  },
48
53
  "dependencies": {
49
54
  "@modelcontextprotocol/sdk": "^1.25.2",
55
+ "@x402/evm": "~2.5.0",
56
+ "@x402/fetch": "~2.5.0",
50
57
  "express": "^5.2.1",
58
+ "viem": "^2.50.4",
51
59
  "zod": "^3.24.2"
52
60
  },
53
61
  "devDependencies": {
62
+ "@types/express": "^5.0.6",
54
63
  "@types/node": "^22.13.10",
55
64
  "typescript": "^5.8.2",
56
65
  "vitest": "^3.0.9"