@quidli/connect-mcp 0.1.1 → 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.
- package/README.md +166 -63
- package/dist/client.d.ts +4 -0
- package/dist/client.js +18 -4
- package/dist/config.d.ts +9 -1
- package/dist/config.js +34 -7
- package/dist/deeplink.js +1 -1
- package/dist/errors.d.ts +2 -1
- package/dist/errors.js +7 -3
- package/dist/mount.js +2 -1
- package/dist/x402-fetch.d.ts +2 -0
- package/dist/x402-fetch.js +30 -0
- package/package.json +10 -1
package/README.md
CHANGED
|
@@ -1,57 +1,128 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Quidli Connect MCP
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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 **or** x402 pay-per-call |
|
|
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
|
-
|
|
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": "
|
|
68
|
+
"x-api-key": "<your-connect-api-key>"
|
|
23
69
|
}
|
|
24
70
|
}
|
|
25
71
|
}
|
|
26
72
|
}
|
|
27
73
|
```
|
|
28
74
|
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
100
|
+
Until a valid API key is set, requests return `401` with setup instructions.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
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.
|
|
42
113
|
|
|
43
|
-
|
|
114
|
+
#### Option A — API key
|
|
44
115
|
|
|
45
|
-
|
|
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,91 @@ Add to `.cursor/mcp.json` (or global MCP settings):
|
|
|
59
130
|
}
|
|
60
131
|
```
|
|
61
132
|
|
|
62
|
-
|
|
133
|
+
#### Option B — x402 pay-per-call (wallet)
|
|
63
134
|
|
|
64
|
-
|
|
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
|
-
```
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
153
|
+
Use `84532` for Base Sepolia when pointing at a staging API.
|
|
73
154
|
|
|
74
|
-
|
|
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
|
+
### Claude Desktop
|
|
79
156
|
|
|
80
|
-
|
|
157
|
+
1. Open **Claude → Settings → Developer → Edit Config** (enable Developer mode first if you do not see it).
|
|
158
|
+
2. Merge one of the blocks below into `mcpServers` in:
|
|
81
159
|
|
|
82
|
-
|
|
160
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
161
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
83
162
|
|
|
84
|
-
|
|
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 |
|
|
163
|
+
3. Restart Claude Desktop.
|
|
93
164
|
|
|
94
|
-
|
|
165
|
+
#### Option A — API key
|
|
95
166
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
-
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"mcpServers": {
|
|
170
|
+
"quidli-connect": {
|
|
171
|
+
"command": "npx",
|
|
172
|
+
"args": ["-y", "@quidli/connect-mcp"],
|
|
173
|
+
"env": {
|
|
174
|
+
"CONNECT_API_KEY": "<your-connect-api-key>",
|
|
175
|
+
"CONNECT_API_BASE_URL": "https://api.connect.quid.li"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
100
181
|
|
|
101
|
-
|
|
182
|
+
#### Option B — x402 pay-per-call (wallet)
|
|
102
183
|
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"mcpServers": {
|
|
187
|
+
"quidli-connect": {
|
|
188
|
+
"command": "npx",
|
|
189
|
+
"args": ["-y", "@quidli/connect-mcp"],
|
|
190
|
+
"env": {
|
|
191
|
+
"EVM_PRIVATE_KEY": "0x<wallet-with-usdc-on-base>",
|
|
192
|
+
"CONNECT_API_BASE_URL": "https://api.connect.quid.li",
|
|
193
|
+
"CONNECT_X402_EVM_NETWORK": "8453"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
109
198
|
```
|
|
110
199
|
|
|
111
|
-
|
|
200
|
+
If both `CONNECT_API_KEY` and `EVM_PRIVATE_KEY` are set, the API key is used.
|
|
201
|
+
|
|
202
|
+
At least one of `CONNECT_API_KEY` or `EVM_PRIVATE_KEY` is required for local mode.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Tools
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
| Tool | What it does |
|
|
210
|
+
| ---------------------------- | --------------------------------------------------------- |
|
|
211
|
+
| `connect_get_price` | List reference prices (no auth) |
|
|
212
|
+
| `connect_lookup` | Resolve social identities to EVM and SOL wallet addresses |
|
|
213
|
+
| `connect_scores_batch` | Batch scores for accounts or usernames |
|
|
214
|
+
| `connect_scores_by_account` | Scores for one linked account |
|
|
215
|
+
| `connect_scores_by_username` | Scores by Connect username |
|
|
216
|
+
| `connect_drop` | Smart Send (batch token transfer) — **API key only** |
|
|
217
|
+
| `connect_agent_prompt` | Natural-language agent for recipients discovery |
|
|
112
218
|
|
|
113
|
-
```bash
|
|
114
|
-
npm publish --access public
|
|
115
|
-
```
|
|
116
219
|
|
|
117
|
-
|
|
220
|
+
Ask your client to use these tools when you need Connect data or actions.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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;
|
|
@@ -11,6 +12,9 @@ export interface ConnectRequestOptions {
|
|
|
11
12
|
}
|
|
12
13
|
export declare class ConnectClient {
|
|
13
14
|
private readonly config;
|
|
15
|
+
private readonly authMode;
|
|
16
|
+
private readonly paidFetch?;
|
|
14
17
|
constructor(config: ConnectClientConfig);
|
|
18
|
+
private resolveFetch;
|
|
15
19
|
request(options: ConnectRequestOptions): Promise<CallToolResult>;
|
|
16
20
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
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.apiKey ? 'api-key' : 'x402';
|
|
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;
|
|
@@ -19,7 +32,7 @@ export class ConnectClient {
|
|
|
19
32
|
const headers = {
|
|
20
33
|
accept: 'application/json',
|
|
21
34
|
};
|
|
22
|
-
if (authenticated) {
|
|
35
|
+
if (authenticated && this.config.apiKey) {
|
|
23
36
|
headers['x-api-key'] = this.config.apiKey;
|
|
24
37
|
}
|
|
25
38
|
if (body !== undefined) {
|
|
@@ -29,8 +42,9 @@ export class ConnectClient {
|
|
|
29
42
|
const timeout = timeoutMs
|
|
30
43
|
? setTimeout(() => controller.abort(), timeoutMs)
|
|
31
44
|
: undefined;
|
|
45
|
+
const httpFetch = this.resolveFetch(authenticated);
|
|
32
46
|
try {
|
|
33
|
-
const response = await
|
|
47
|
+
const response = await httpFetch(url, {
|
|
34
48
|
method,
|
|
35
49
|
headers,
|
|
36
50
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
@@ -39,9 +53,9 @@ export class ConnectClient {
|
|
|
39
53
|
const raw = await response.json().catch(() => ({}));
|
|
40
54
|
if (!response.ok) {
|
|
41
55
|
if (response.status === 504) {
|
|
42
|
-
return errorResult(mapHttpError(504, raw));
|
|
56
|
+
return errorResult(mapHttpError(504, raw, this.authMode));
|
|
43
57
|
}
|
|
44
|
-
return errorResult(mapHttpError(response.status, raw));
|
|
58
|
+
return errorResult(mapHttpError(response.status, raw, this.authMode));
|
|
45
59
|
}
|
|
46
60
|
return successResult({
|
|
47
61
|
httpStatus: response.status,
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export interface ConnectClientConfig {
|
|
2
2
|
baseUrl: string;
|
|
3
|
-
|
|
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,7 @@ export interface HttpServerConfig {
|
|
|
8
13
|
baseUrl: string;
|
|
9
14
|
}
|
|
10
15
|
export declare function loadHttpServerConfig(env?: NodeJS.ProcessEnv): HttpServerConfig;
|
|
16
|
+
/** Stdio MCP: CONNECT_API_KEY and/or EVM_PRIVATE_KEY (at least one required). */
|
|
11
17
|
export declare function loadConfig(env?: NodeJS.ProcessEnv): ConnectClientConfig;
|
|
18
|
+
/** Hosted HTTP MCP: api-key from request header only. */
|
|
19
|
+
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,28 @@ 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
|
-
|
|
12
|
-
return { host, port, baseUrl };
|
|
24
|
+
return { host, port, baseUrl: loadBaseUrl(env) };
|
|
13
25
|
}
|
|
26
|
+
/** Stdio MCP: CONNECT_API_KEY and/or EVM_PRIVATE_KEY (at least one required). */
|
|
14
27
|
export function loadConfig(env = process.env) {
|
|
15
|
-
const apiKey = env.CONNECT_API_KEY?.trim();
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
const apiKey = env.CONNECT_API_KEY?.trim() || undefined;
|
|
29
|
+
const evmPrivateKey = normalizeEvmPrivateKey(env.EVM_PRIVATE_KEY);
|
|
30
|
+
const x402EvmNetwork = parseX402EvmNetwork(env);
|
|
31
|
+
if (!apiKey && !evmPrivateKey) {
|
|
32
|
+
throw new Error('CONNECT_API_KEY or EVM_PRIVATE_KEY is required. Get an API key at https://connect.quid.li (Enable API access), or set EVM_PRIVATE_KEY for pay-per-call x402.');
|
|
18
33
|
}
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
return {
|
|
35
|
+
baseUrl: loadBaseUrl(env),
|
|
36
|
+
apiKey,
|
|
37
|
+
evmPrivateKey: apiKey ? undefined : evmPrivateKey,
|
|
38
|
+
x402EvmNetwork,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Hosted HTTP MCP: api-key from request header only. */
|
|
42
|
+
export function createHostedClientConfig(apiKey, baseUrl) {
|
|
43
|
+
return {
|
|
44
|
+
baseUrl,
|
|
45
|
+
apiKey,
|
|
46
|
+
x402EvmNetwork: DEFAULT_X402_EVM_NETWORK,
|
|
47
|
+
};
|
|
21
48
|
}
|
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
|
|
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
|
|
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
|
|
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:
|
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(
|
|
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,
|
|
@@ -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.
|
|
3
|
+
"version": "0.2.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"
|
|
@@ -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"
|