@quidli/connect-mcp 0.1.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 +143 -0
- package/dist/client.d.ts +16 -0
- package/dist/client.js +64 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +21 -0
- package/dist/create-mcp-server.d.ts +3 -0
- package/dist/create-mcp-server.js +12 -0
- package/dist/deeplink.d.ts +8 -0
- package/dist/deeplink.js +13 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +31 -0
- package/dist/http-auth.d.ts +13 -0
- package/dist/http-auth.js +32 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +19 -0
- package/dist/index-http.d.ts +2 -0
- package/dist/index-http.js +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/metadata.d.ts +13 -0
- package/dist/metadata.js +20 -0
- package/dist/mount.d.ts +8 -0
- package/dist/mount.js +70 -0
- package/dist/request-host.d.ts +2 -0
- package/dist/request-host.js +13 -0
- package/dist/result.d.ts +3 -0
- package/dist/result.js +11 -0
- package/dist/schemas.d.ts +86 -0
- package/dist/schemas.js +73 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +9 -0
- package/dist/tools/register.d.ts +4 -0
- package/dist/tools/register.js +49 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @quidli/connect-mcp
|
|
2
|
+
|
|
3
|
+
MCP server exposing [Quidli Connect](https://connect.quid.li) product APIs as tools for Cursor, Claude Desktop, and other MCP hosts.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 20+
|
|
8
|
+
- A Connect API key ([Enable API access](https://connect.quid.li))
|
|
9
|
+
|
|
10
|
+
## Cursor configuration
|
|
11
|
+
|
|
12
|
+
### Remote URL (recommended for production)
|
|
13
|
+
|
|
14
|
+
Same pattern as GitHub MCP — Cursor connects to a hosted endpoint and sends your API key in headers:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"connect": {
|
|
20
|
+
"url": "https://mcp.connect.quid.li",
|
|
21
|
+
"headers": {
|
|
22
|
+
"x-api-key": "set-your-api-key-here"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
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):
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
cursor://anysphere.cursor-deeplink/mcp/install?name=connect&config=eyJ1cmwiOiJodHRwczovL21jcC5jb25uZWN0LnF1aWQubGkIiwiaGVhZGVycyI6eyJ4LWFwaS1rZXkiOiJzZXQteW91ci1hcGkta2V5LWhlcmUifX0=
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or generate programmatically:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { buildCursorMcpInstallDeeplink } from '@quidli/connect-mcp/deeplink';
|
|
41
|
+
// buildCursorMcpInstallDeeplink() in published package once exported — see src/deeplink.ts
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Local stdio (npm / development)
|
|
45
|
+
|
|
46
|
+
Add to `.cursor/mcp.json` (or global MCP settings):
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"connect": {
|
|
52
|
+
"command": "npx",
|
|
53
|
+
"args": ["-y", "@quidli/connect-mcp"],
|
|
54
|
+
"env": {
|
|
55
|
+
"CONNECT_API_KEY": "<your-api-key>",
|
|
56
|
+
"CONNECT_API_BASE_URL": "https://api.connect.quid.li"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For local development from this monorepo:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"connect-local": {
|
|
69
|
+
"command": "node",
|
|
70
|
+
"args": ["/absolute/path/to/connect-backend/packages/connect-mcp/dist/index.js"],
|
|
71
|
+
"env": {
|
|
72
|
+
"CONNECT_API_KEY": "<your-api-key>",
|
|
73
|
+
"CONNECT_API_BASE_URL": "https://api.staging.connect.quid.li"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`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.
|
|
81
|
+
|
|
82
|
+
## HTTP server (hosted deployment)
|
|
83
|
+
|
|
84
|
+
### Embedded in connect-backend (production)
|
|
85
|
+
|
|
86
|
+
MCP runs inside the **same Cloud Run service** as `api.connect.quid.li`. Map `mcp.connect.quid.li` as an additional custom domain on that service. Requests with MCP `Host` headers are routed to the MCP handler; API traffic is unchanged.
|
|
87
|
+
|
|
88
|
+
See `docs/ai/deployment/feature-connect-api-mcp.md` in connect-backend for DNS, env vars, and smoke tests.
|
|
89
|
+
|
|
90
|
+
### Standalone binary (local / debugging)
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cd packages/connect-mcp
|
|
94
|
+
npm run build
|
|
95
|
+
CONNECT_API_BASE_URL=https://api.connect.quid.li npm run start:http
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Environment:
|
|
99
|
+
|
|
100
|
+
| Variable | Default | Description |
|
|
101
|
+
|----------|---------|-------------|
|
|
102
|
+
| `CONNECT_MCP_HTTP_HOST` | `0.0.0.0` | Bind address |
|
|
103
|
+
| `CONNECT_MCP_HTTP_PORT` | `8080` | Listen port |
|
|
104
|
+
| `CONNECT_API_BASE_URL` | `https://api.connect.quid.li` | Upstream Connect API |
|
|
105
|
+
|
|
106
|
+
Health check: `GET /health`
|
|
107
|
+
|
|
108
|
+
## Tools (7)
|
|
109
|
+
|
|
110
|
+
| Tool | HTTP | Auth |
|
|
111
|
+
|------|------|------|
|
|
112
|
+
| `connect_get_price` | `GET /price` | none |
|
|
113
|
+
| `connect_lookup` | `POST /lookup` | api-key |
|
|
114
|
+
| `connect_scores_batch` | `POST /scores` | api-key |
|
|
115
|
+
| `connect_scores_by_account` | `GET /scores/:platform/:identifier` | api-key |
|
|
116
|
+
| `connect_scores_by_username` | `GET /scores/u/:username` | api-key |
|
|
117
|
+
| `connect_drop` | `POST /drop` | api-key |
|
|
118
|
+
| `connect_agent_prompt` | `POST /agent` | api-key |
|
|
119
|
+
|
|
120
|
+
Not exposed: `POST /lookup/exposed`, agent feedback, member/admin routes.
|
|
121
|
+
|
|
122
|
+
## Auth notes
|
|
123
|
+
|
|
124
|
+
- **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).
|
|
125
|
+
- `connect_get_price` is public on the HTTP API and does not send `x-api-key`.
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
cd packages/connect-mcp
|
|
131
|
+
npm install
|
|
132
|
+
npm test
|
|
133
|
+
npm run build
|
|
134
|
+
CONNECT_API_KEY=<key> npm start
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Publish
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm publish --access public
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
(Requires `@quidli` npm org access.)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ConnectClientConfig } from './config.js';
|
|
2
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
export type HttpMethod = 'GET' | 'POST';
|
|
4
|
+
export interface ConnectRequestOptions {
|
|
5
|
+
method: HttpMethod;
|
|
6
|
+
path: string;
|
|
7
|
+
authenticated?: boolean;
|
|
8
|
+
body?: unknown;
|
|
9
|
+
query?: Record<string, string | undefined>;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class ConnectClient {
|
|
13
|
+
private readonly config;
|
|
14
|
+
constructor(config: ConnectClientConfig);
|
|
15
|
+
request(options: ConnectRequestOptions): Promise<CallToolResult>;
|
|
16
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { mapHttpError } from './errors.js';
|
|
2
|
+
import { errorResult, successResult } from './result.js';
|
|
3
|
+
export class ConnectClient {
|
|
4
|
+
config;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
}
|
|
8
|
+
async request(options) {
|
|
9
|
+
const { method, path, body, query, timeoutMs } = options;
|
|
10
|
+
const authenticated = options.authenticated ?? true;
|
|
11
|
+
const url = new URL(path, `${this.config.baseUrl}/`);
|
|
12
|
+
if (query) {
|
|
13
|
+
for (const [key, value] of Object.entries(query)) {
|
|
14
|
+
if (value !== undefined) {
|
|
15
|
+
url.searchParams.set(key, value);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const headers = {
|
|
20
|
+
accept: 'application/json',
|
|
21
|
+
};
|
|
22
|
+
if (authenticated) {
|
|
23
|
+
headers['x-api-key'] = this.config.apiKey;
|
|
24
|
+
}
|
|
25
|
+
if (body !== undefined) {
|
|
26
|
+
headers['content-type'] = 'application/json';
|
|
27
|
+
}
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timeout = timeoutMs
|
|
30
|
+
? setTimeout(() => controller.abort(), timeoutMs)
|
|
31
|
+
: undefined;
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
method,
|
|
35
|
+
headers,
|
|
36
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
37
|
+
signal: controller.signal,
|
|
38
|
+
});
|
|
39
|
+
const raw = await response.json().catch(() => ({}));
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
if (response.status === 504) {
|
|
42
|
+
return errorResult(mapHttpError(504, raw));
|
|
43
|
+
}
|
|
44
|
+
return errorResult(mapHttpError(response.status, raw));
|
|
45
|
+
}
|
|
46
|
+
return successResult({
|
|
47
|
+
httpStatus: response.status,
|
|
48
|
+
...(typeof raw === 'object' && raw !== null ? raw : { data: raw }),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
53
|
+
return errorResult('Request timed out');
|
|
54
|
+
}
|
|
55
|
+
const message = error instanceof Error ? error.message : 'Network error';
|
|
56
|
+
return errorResult(message);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
if (timeout) {
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ConnectClientConfig {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
}
|
|
5
|
+
export interface HttpServerConfig {
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function loadHttpServerConfig(env?: NodeJS.ProcessEnv): HttpServerConfig;
|
|
11
|
+
export declare function loadConfig(env?: NodeJS.ProcessEnv): ConnectClientConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = 'https://api.connect.quid.li';
|
|
2
|
+
const DEFAULT_HTTP_HOST = '0.0.0.0';
|
|
3
|
+
const DEFAULT_HTTP_PORT = 8080;
|
|
4
|
+
export function loadHttpServerConfig(env = process.env) {
|
|
5
|
+
const host = env.CONNECT_MCP_HTTP_HOST?.trim() || DEFAULT_HTTP_HOST;
|
|
6
|
+
const portRaw = env.CONNECT_MCP_HTTP_PORT?.trim() || String(DEFAULT_HTTP_PORT);
|
|
7
|
+
const port = Number(portRaw);
|
|
8
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
9
|
+
throw new Error(`CONNECT_MCP_HTTP_PORT must be a valid port (got "${portRaw}")`);
|
|
10
|
+
}
|
|
11
|
+
const baseUrl = (env.CONNECT_API_BASE_URL?.trim() || DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
12
|
+
return { host, port, baseUrl };
|
|
13
|
+
}
|
|
14
|
+
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 };
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { ConnectClient } from './client.js';
|
|
3
|
+
import { CONNECT_MCP_SERVER_INFO } from './metadata.js';
|
|
4
|
+
import { registerTools } from './tools/register.js';
|
|
5
|
+
export function createMcpServer(config) {
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
...CONNECT_MCP_SERVER_INFO,
|
|
8
|
+
});
|
|
9
|
+
const client = new ConnectClient(config);
|
|
10
|
+
registerTools(server, client);
|
|
11
|
+
return server;
|
|
12
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface CursorMcpRemoteConfig {
|
|
2
|
+
url: string;
|
|
3
|
+
headers: {
|
|
4
|
+
'x-api-key': string;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export declare function buildCursorRemoteMcpConfig(apiKey?: string): CursorMcpRemoteConfig;
|
|
8
|
+
export declare function buildCursorMcpInstallDeeplink(serverName?: string, config?: CursorMcpRemoteConfig): string;
|
package/dist/deeplink.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CONNECT_MCP_API_KEY_PLACEHOLDER, CONNECT_MCP_REMOTE_URL } from './metadata.js';
|
|
2
|
+
export function buildCursorRemoteMcpConfig(apiKey = CONNECT_MCP_API_KEY_PLACEHOLDER) {
|
|
3
|
+
return {
|
|
4
|
+
url: CONNECT_MCP_REMOTE_URL,
|
|
5
|
+
headers: {
|
|
6
|
+
'x-api-key': apiKey,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function buildCursorMcpInstallDeeplink(serverName = 'connect', config = buildCursorRemoteMcpConfig()) {
|
|
11
|
+
const encoded = Buffer.from(JSON.stringify(config), 'utf8').toString('base64');
|
|
12
|
+
return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(serverName)}&config=${encoded}`;
|
|
13
|
+
}
|
package/dist/errors.d.ts
ADDED
package/dist/errors.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function parseNestErrorMessage(body) {
|
|
2
|
+
if (body && typeof body === 'object' && 'message' in body) {
|
|
3
|
+
const message = body.message;
|
|
4
|
+
if (Array.isArray(message)) {
|
|
5
|
+
return message.map(String).join(' ');
|
|
6
|
+
}
|
|
7
|
+
if (typeof message === 'string') {
|
|
8
|
+
return message;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return 'Request failed';
|
|
12
|
+
}
|
|
13
|
+
export function mapHttpError(status, body) {
|
|
14
|
+
const detail = parseNestErrorMessage(body);
|
|
15
|
+
switch (status) {
|
|
16
|
+
case 400:
|
|
17
|
+
return detail;
|
|
18
|
+
case 401:
|
|
19
|
+
return `Unauthorized: ${detail}. Verify CONNECT_API_KEY at https://connect.quid.li`;
|
|
20
|
+
case 402:
|
|
21
|
+
return `Payment required (${detail}). MCP requires a valid Connect API key — x402 is not supported in MCP.`;
|
|
22
|
+
case 404:
|
|
23
|
+
return detail;
|
|
24
|
+
case 504:
|
|
25
|
+
return detail === 'Request failed'
|
|
26
|
+
? 'Upstream agent did not respond within 60 seconds'
|
|
27
|
+
: detail;
|
|
28
|
+
default:
|
|
29
|
+
return status >= 500 ? `Connect API error (${status}): ${detail}` : detail;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
export interface RequestApiKeyResult {
|
|
3
|
+
ok: true;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
}
|
|
6
|
+
export interface RequestApiKeyError {
|
|
7
|
+
ok: false;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
export type RequestApiKeyValidation = RequestApiKeyResult | RequestApiKeyError;
|
|
11
|
+
export declare function extractRequestApiKey(req: IncomingMessage): string | undefined;
|
|
12
|
+
export declare function isPlaceholderApiKey(apiKey: string): boolean;
|
|
13
|
+
export declare function validateRequestApiKey(req: IncomingMessage): RequestApiKeyValidation;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CONNECT_MCP_API_KEY_PLACEHOLDER } from './metadata.js';
|
|
2
|
+
const PLACEHOLDER_API_KEYS = new Set([
|
|
3
|
+
'your-connect-api-key',
|
|
4
|
+
'<your-api-key>',
|
|
5
|
+
'set-your-api-key-here',
|
|
6
|
+
...(CONNECT_MCP_API_KEY_PLACEHOLDER ? [CONNECT_MCP_API_KEY_PLACEHOLDER] : []),
|
|
7
|
+
]);
|
|
8
|
+
export function extractRequestApiKey(req) {
|
|
9
|
+
const raw = req.headers['x-api-key'];
|
|
10
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
11
|
+
const trimmed = value?.trim();
|
|
12
|
+
return trimmed || undefined;
|
|
13
|
+
}
|
|
14
|
+
export function isPlaceholderApiKey(apiKey) {
|
|
15
|
+
return PLACEHOLDER_API_KEYS.has(apiKey);
|
|
16
|
+
}
|
|
17
|
+
export function validateRequestApiKey(req) {
|
|
18
|
+
const apiKey = extractRequestApiKey(req);
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
message: 'Missing x-api-key header. Get a key at https://connect.quid.li (Enable API access) and set it in your MCP headers.',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (isPlaceholderApiKey(apiKey)) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
message: 'Invalid API key. Set your Connect API key in Cursor MCP settings (headers.x-api-key). Get one at https://connect.quid.li',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { ok: true, apiKey };
|
|
32
|
+
}
|
package/dist/http.d.ts
ADDED
package/dist/http.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
|
|
2
|
+
import { loadHttpServerConfig } from './config.js';
|
|
3
|
+
import { mountConnectMcpHttp } from './mount.js';
|
|
4
|
+
export { isConnectMcpHost, mountConnectMcpHttp, resolveEmbeddedApiBaseUrl } from './mount.js';
|
|
5
|
+
export async function main() {
|
|
6
|
+
const { host, port, baseUrl } = loadHttpServerConfig();
|
|
7
|
+
const app = createMcpExpressApp({ host, allowedHosts: ['mcp.connect.quid.li', 'localhost'] });
|
|
8
|
+
mountConnectMcpHttp(app, { baseUrl });
|
|
9
|
+
await new Promise((resolve, reject) => {
|
|
10
|
+
app.listen(port, host, (error) => {
|
|
11
|
+
if (error) {
|
|
12
|
+
reject(error);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
console.log(`Quidli Connect MCP HTTP server listening on http://${host}:${port}/`);
|
|
16
|
+
resolve();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const CONNECT_MCP_API_KEY_PLACEHOLDER = "";
|
|
2
|
+
export declare const CONNECT_MCP_REMOTE_URL = "https://mcp.connect.quid.li";
|
|
3
|
+
export declare const CONNECT_MCP_SERVER_INFO: {
|
|
4
|
+
name: string;
|
|
5
|
+
title: string;
|
|
6
|
+
version: string;
|
|
7
|
+
websiteUrl: string;
|
|
8
|
+
icons: {
|
|
9
|
+
src: string;
|
|
10
|
+
mimeType: string;
|
|
11
|
+
sizes: string[];
|
|
12
|
+
}[];
|
|
13
|
+
};
|
package/dist/metadata.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const CONNECT_MCP_API_KEY_PLACEHOLDER = '';
|
|
2
|
+
export const CONNECT_MCP_REMOTE_URL = 'https://mcp.connect.quid.li';
|
|
3
|
+
export const CONNECT_MCP_SERVER_INFO = {
|
|
4
|
+
name: 'quidli-connect',
|
|
5
|
+
title: 'Quidli Connect',
|
|
6
|
+
version: '0.1.0',
|
|
7
|
+
websiteUrl: 'https://connect.quid.li',
|
|
8
|
+
icons: [
|
|
9
|
+
{
|
|
10
|
+
src: 'https://connect.quid.li/theme/dark/squid.png',
|
|
11
|
+
mimeType: 'image/png',
|
|
12
|
+
sizes: ['48x48'],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
src: 'https://assets.quid.li/icons/quidli/quidli_square.png',
|
|
16
|
+
mimeType: 'image/png',
|
|
17
|
+
sizes: ['64x64'],
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
};
|
package/dist/mount.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IRouter } from 'express';
|
|
2
|
+
export { resolveRequestHost } from './request-host.js';
|
|
3
|
+
export interface ConnectMcpMountOptions {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolveEmbeddedApiBaseUrl(port: number, env?: NodeJS.ProcessEnv): string;
|
|
7
|
+
export declare function isConnectMcpHost(hostname: string, env?: NodeJS.ProcessEnv): boolean;
|
|
8
|
+
export declare function mountConnectMcpHttp(router: IRouter, options: ConnectMcpMountOptions): void;
|
package/dist/mount.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
2
|
+
import { createMcpServer } from './create-mcp-server.js';
|
|
3
|
+
import { validateRequestApiKey } from './http-auth.js';
|
|
4
|
+
export { resolveRequestHost } from './request-host.js';
|
|
5
|
+
function sendJsonRpcError(res, status, message) {
|
|
6
|
+
if (res.headersSent) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
res.status(status).json({
|
|
10
|
+
jsonrpc: '2.0',
|
|
11
|
+
error: {
|
|
12
|
+
code: -32001,
|
|
13
|
+
message,
|
|
14
|
+
},
|
|
15
|
+
id: null,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function resolveEmbeddedApiBaseUrl(port, env = process.env) {
|
|
19
|
+
const fromEnv = env.CONNECT_MCP_API_BASE_URL?.trim() || env.CONNECT_API_BASE_URL?.trim();
|
|
20
|
+
if (fromEnv) {
|
|
21
|
+
return fromEnv.replace(/\/$/, '');
|
|
22
|
+
}
|
|
23
|
+
return `http://127.0.0.1:${port}`;
|
|
24
|
+
}
|
|
25
|
+
export function isConnectMcpHost(hostname, env = process.env) {
|
|
26
|
+
const configured = env.CONNECT_MCP_HOSTS?.split(',')
|
|
27
|
+
.map((host) => host.trim().toLowerCase())
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
const normalized = hostname.toLowerCase();
|
|
30
|
+
if (configured?.length) {
|
|
31
|
+
return configured.includes(normalized);
|
|
32
|
+
}
|
|
33
|
+
return (normalized === 'mcp.connect.quid.li' ||
|
|
34
|
+
normalized === 'mcp.staging.connect.quid.li' ||
|
|
35
|
+
normalized.endsWith('.mcp.connect.quid.li'));
|
|
36
|
+
}
|
|
37
|
+
export function mountConnectMcpHttp(router, options) {
|
|
38
|
+
const { baseUrl } = options;
|
|
39
|
+
router.get('/health', (_req, res) => {
|
|
40
|
+
res.json({ ok: true, service: 'quidli-connect-mcp' });
|
|
41
|
+
});
|
|
42
|
+
// Stateless mode: each request creates a fresh server+transport.
|
|
43
|
+
// No in-memory session map, so Cloud Run restarts / multiple instances
|
|
44
|
+
// never cause "Missing or invalid MCP session" errors.
|
|
45
|
+
router.all('/', async (req, res) => {
|
|
46
|
+
const auth = validateRequestApiKey(req);
|
|
47
|
+
if (!auth.ok) {
|
|
48
|
+
sendJsonRpcError(res, 401, auth.message);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const server = createMcpServer({ apiKey: auth.apiKey, baseUrl });
|
|
53
|
+
const transport = new StreamableHTTPServerTransport({
|
|
54
|
+
sessionIdGenerator: undefined, // stateless — no session tracking
|
|
55
|
+
enableJsonResponse: true,
|
|
56
|
+
});
|
|
57
|
+
await server.connect(transport);
|
|
58
|
+
await transport.handleRequest(req, res, req.body);
|
|
59
|
+
res.on('close', () => {
|
|
60
|
+
transport.close().catch(() => undefined);
|
|
61
|
+
server.close().catch(() => undefined);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : 'Internal server error';
|
|
66
|
+
console.error('MCP HTTP error:', message);
|
|
67
|
+
sendJsonRpcError(res, 500, message);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function resolveRequestHost(req) {
|
|
2
|
+
const forwarded = req.headers['x-forwarded-host'];
|
|
3
|
+
const forwardedHost = Array.isArray(forwarded) ? forwarded[0] : forwarded;
|
|
4
|
+
if (forwardedHost) {
|
|
5
|
+
return forwardedHost.split(',')[0]?.trim().split(':')[0]?.toLowerCase() ?? '';
|
|
6
|
+
}
|
|
7
|
+
const host = req.headers.host;
|
|
8
|
+
if (host) {
|
|
9
|
+
return host.split(':')[0]?.toLowerCase() ?? '';
|
|
10
|
+
}
|
|
11
|
+
const hostname = 'hostname' in req && typeof req.hostname === 'string' ? req.hostname : '';
|
|
12
|
+
return hostname.toLowerCase();
|
|
13
|
+
}
|
package/dist/result.d.ts
ADDED
package/dist/result.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Unified recipient — API validates fully; MCP forwards JSON. */
|
|
3
|
+
export declare const linkedAccountSchema: z.ZodObject<{
|
|
4
|
+
type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
|
|
5
|
+
id: z.ZodOptional<z.ZodString>;
|
|
6
|
+
username: z.ZodOptional<z.ZodString>;
|
|
7
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
8
|
+
type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
|
|
9
|
+
id: z.ZodOptional<z.ZodString>;
|
|
10
|
+
username: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
12
|
+
type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
|
|
13
|
+
id: z.ZodOptional<z.ZodString>;
|
|
14
|
+
username: z.ZodOptional<z.ZodString>;
|
|
15
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
16
|
+
export declare const lookupInputSchema: {
|
|
17
|
+
recipients: z.ZodArray<z.ZodObject<{
|
|
18
|
+
type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
|
|
19
|
+
id: z.ZodOptional<z.ZodString>;
|
|
20
|
+
username: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
22
|
+
type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
|
|
23
|
+
id: z.ZodOptional<z.ZodString>;
|
|
24
|
+
username: z.ZodOptional<z.ZodString>;
|
|
25
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
26
|
+
type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
|
|
27
|
+
id: z.ZodOptional<z.ZodString>;
|
|
28
|
+
username: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
30
|
+
};
|
|
31
|
+
export declare const scoresBatchInputSchema: {
|
|
32
|
+
users: z.ZodArray<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">>, "many">;
|
|
45
|
+
filter: z.ZodOptional<z.ZodObject<{
|
|
46
|
+
type: z.ZodEnum<["quidli_score", "lens_score", "neynar_score", "ethos_twitter_reputation", "ethos_wallet_reputation"]>;
|
|
47
|
+
minScore: z.ZodNumber;
|
|
48
|
+
}, "strip", z.ZodTypeAny, {
|
|
49
|
+
type: "quidli_score" | "lens_score" | "neynar_score" | "ethos_twitter_reputation" | "ethos_wallet_reputation";
|
|
50
|
+
minScore: number;
|
|
51
|
+
}, {
|
|
52
|
+
type: "quidli_score" | "lens_score" | "neynar_score" | "ethos_twitter_reputation" | "ethos_wallet_reputation";
|
|
53
|
+
minScore: number;
|
|
54
|
+
}>>;
|
|
55
|
+
};
|
|
56
|
+
export declare const scoresByAccountInputSchema: {
|
|
57
|
+
platform: z.ZodEnum<["wallet", "email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack"]>;
|
|
58
|
+
identifier: z.ZodString;
|
|
59
|
+
};
|
|
60
|
+
export declare const scoresByUsernameInputSchema: {
|
|
61
|
+
username: z.ZodString;
|
|
62
|
+
};
|
|
63
|
+
export declare const dropInputSchema: {
|
|
64
|
+
idempotencyKey: z.ZodString;
|
|
65
|
+
chainId: z.ZodNumber;
|
|
66
|
+
tokenContract: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
67
|
+
recipients: z.ZodArray<z.ZodObject<{
|
|
68
|
+
type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
|
|
69
|
+
id: z.ZodOptional<z.ZodString>;
|
|
70
|
+
username: z.ZodOptional<z.ZodString>;
|
|
71
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
72
|
+
type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
|
|
73
|
+
id: z.ZodOptional<z.ZodString>;
|
|
74
|
+
username: z.ZodOptional<z.ZodString>;
|
|
75
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
76
|
+
type: z.ZodEnum<["email", "phone", "telegram", "discord", "farcaster", "twitter", "github", "linkedin", "slack", "wallet", "username"]>;
|
|
77
|
+
id: z.ZodOptional<z.ZodString>;
|
|
78
|
+
username: z.ZodOptional<z.ZodString>;
|
|
79
|
+
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
80
|
+
amountInWeiPerRecipient: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
81
|
+
ignoreFailedRecipients: z.ZodOptional<z.ZodBoolean>;
|
|
82
|
+
};
|
|
83
|
+
export declare const agentPromptInputSchema: {
|
|
84
|
+
prompt: z.ZodString;
|
|
85
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
86
|
+
};
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const recipientTypeSchema = z.enum([
|
|
3
|
+
'email',
|
|
4
|
+
'phone',
|
|
5
|
+
'telegram',
|
|
6
|
+
'discord',
|
|
7
|
+
'farcaster',
|
|
8
|
+
'twitter',
|
|
9
|
+
'github',
|
|
10
|
+
'linkedin',
|
|
11
|
+
'slack',
|
|
12
|
+
'wallet',
|
|
13
|
+
'username',
|
|
14
|
+
]);
|
|
15
|
+
/** Unified recipient — API validates fully; MCP forwards JSON. */
|
|
16
|
+
export const linkedAccountSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
type: recipientTypeSchema,
|
|
19
|
+
id: z.string().optional(),
|
|
20
|
+
username: z.string().optional(),
|
|
21
|
+
})
|
|
22
|
+
.passthrough();
|
|
23
|
+
export const lookupInputSchema = {
|
|
24
|
+
recipients: z
|
|
25
|
+
.array(linkedAccountSchema)
|
|
26
|
+
.min(1)
|
|
27
|
+
.describe('Recipients to resolve to EVM/Solana wallets'),
|
|
28
|
+
};
|
|
29
|
+
export const scoresBatchInputSchema = {
|
|
30
|
+
users: z.array(linkedAccountSchema).min(1).describe('Users to fetch scores for'),
|
|
31
|
+
filter: z
|
|
32
|
+
.object({
|
|
33
|
+
type: z.enum([
|
|
34
|
+
'quidli_score',
|
|
35
|
+
'lens_score',
|
|
36
|
+
'neynar_score',
|
|
37
|
+
'ethos_twitter_reputation',
|
|
38
|
+
'ethos_wallet_reputation',
|
|
39
|
+
]),
|
|
40
|
+
minScore: z.number(),
|
|
41
|
+
})
|
|
42
|
+
.optional(),
|
|
43
|
+
};
|
|
44
|
+
export const scoresByAccountInputSchema = {
|
|
45
|
+
platform: z.enum([
|
|
46
|
+
'wallet',
|
|
47
|
+
'email',
|
|
48
|
+
'phone',
|
|
49
|
+
'telegram',
|
|
50
|
+
'discord',
|
|
51
|
+
'farcaster',
|
|
52
|
+
'twitter',
|
|
53
|
+
'github',
|
|
54
|
+
'linkedin',
|
|
55
|
+
'slack',
|
|
56
|
+
]),
|
|
57
|
+
identifier: z.string().min(1),
|
|
58
|
+
};
|
|
59
|
+
export const scoresByUsernameInputSchema = {
|
|
60
|
+
username: z.string().min(1),
|
|
61
|
+
};
|
|
62
|
+
export const dropInputSchema = {
|
|
63
|
+
idempotencyKey: z.string().uuid(),
|
|
64
|
+
chainId: z.number().int(),
|
|
65
|
+
tokenContract: z.string().nullable().optional(),
|
|
66
|
+
recipients: z.array(linkedAccountSchema).min(1),
|
|
67
|
+
amountInWeiPerRecipient: z.string().optional().nullable(),
|
|
68
|
+
ignoreFailedRecipients: z.boolean().optional(),
|
|
69
|
+
};
|
|
70
|
+
export const agentPromptInputSchema = {
|
|
71
|
+
prompt: z.string().min(1).max(4000),
|
|
72
|
+
sessionId: z.string().uuid().optional(),
|
|
73
|
+
};
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(): Promise<void>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
2
|
+
import { createMcpServer } from './create-mcp-server.js';
|
|
3
|
+
import { loadConfig } from './config.js';
|
|
4
|
+
export async function main() {
|
|
5
|
+
const config = loadConfig();
|
|
6
|
+
const server = createMcpServer(config);
|
|
7
|
+
const transport = new StdioServerTransport();
|
|
8
|
+
await server.connect(transport);
|
|
9
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
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"];
|
|
4
|
+
export declare function registerTools(server: McpServer, client: ConnectClient): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { agentPromptInputSchema, dropInputSchema, lookupInputSchema, scoresBatchInputSchema, scoresByAccountInputSchema, scoresByUsernameInputSchema, } from '../schemas.js';
|
|
2
|
+
const AGENT_TIMEOUT_MS = 65_000;
|
|
3
|
+
export const CONNECT_MCP_TOOL_NAMES = [
|
|
4
|
+
'connect_get_price',
|
|
5
|
+
'connect_lookup',
|
|
6
|
+
'connect_scores_batch',
|
|
7
|
+
'connect_scores_by_account',
|
|
8
|
+
'connect_scores_by_username',
|
|
9
|
+
'connect_drop',
|
|
10
|
+
'connect_agent_prompt',
|
|
11
|
+
];
|
|
12
|
+
export function registerTools(server, client) {
|
|
13
|
+
server.tool('connect_get_price', 'Get public x402 list prices for lookup, scores, and agent (reference only; live paywall amounts are in 402 responses).', {}, async () => client.request({ method: 'GET', path: '/price', authenticated: false }));
|
|
14
|
+
server.tool('connect_lookup', 'Resolve social identities to EVM and Solana wallet addresses. If status is processing, retry the same payload.', lookupInputSchema, async ({ recipients }) => client.request({
|
|
15
|
+
method: 'POST',
|
|
16
|
+
path: '/lookup',
|
|
17
|
+
body: { recipients },
|
|
18
|
+
}));
|
|
19
|
+
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
|
+
method: 'POST',
|
|
21
|
+
path: '/scores',
|
|
22
|
+
body: { users, ...(filter ? { filter } : {}) },
|
|
23
|
+
}));
|
|
24
|
+
server.tool('connect_scores_by_account', 'Scores for a linked social account or wallet.', scoresByAccountInputSchema, async ({ platform, identifier }) => client.request({
|
|
25
|
+
method: 'GET',
|
|
26
|
+
path: `/scores/${encodeURIComponent(platform)}/${encodeURIComponent(identifier)}`,
|
|
27
|
+
}));
|
|
28
|
+
server.tool('connect_scores_by_username', 'Scores by Connect public username.', scoresByUsernameInputSchema, async ({ username }) => client.request({
|
|
29
|
+
method: 'GET',
|
|
30
|
+
path: `/scores/u/${encodeURIComponent(username.trim())}`,
|
|
31
|
+
}));
|
|
32
|
+
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
|
+
method: 'POST',
|
|
34
|
+
path: '/drop',
|
|
35
|
+
body,
|
|
36
|
+
query: ignoreFailedRecipients === true
|
|
37
|
+
? { ignoreFailedRecipients: 'true' }
|
|
38
|
+
: undefined,
|
|
39
|
+
}));
|
|
40
|
+
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
|
+
method: 'POST',
|
|
42
|
+
path: '/agent',
|
|
43
|
+
body: {
|
|
44
|
+
prompt,
|
|
45
|
+
...(sessionId ? { sessionId } : {}),
|
|
46
|
+
},
|
|
47
|
+
timeoutMs: AGENT_TIMEOUT_MS,
|
|
48
|
+
}));
|
|
49
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quidli/connect-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Quidli Connect product APIs",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"quidli",
|
|
9
|
+
"connect",
|
|
10
|
+
"web3",
|
|
11
|
+
"identity",
|
|
12
|
+
"ai",
|
|
13
|
+
"agent"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://connect.quid.li",
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "Quidli",
|
|
18
|
+
"url": "https://quid.xyz"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"connect-mcp": "dist/index.js",
|
|
23
|
+
"connect-mcp-http": "dist/index-http.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"!dist/**/*.test.js",
|
|
28
|
+
"!dist/**/*.test.d.ts",
|
|
29
|
+
"!dist/**/*.test.js.map"
|
|
30
|
+
],
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./dist/index.js",
|
|
33
|
+
"./mount": "./dist/mount.js",
|
|
34
|
+
"./http": "./dist/http.js"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc && chmod +x dist/index.js dist/index-http.js",
|
|
38
|
+
"prepublishOnly": "npm run build",
|
|
39
|
+
"start": "node dist/index.js",
|
|
40
|
+
"start:http": "node dist/index-http.js",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"test:cov": "vitest run --coverage"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
50
|
+
"express": "^5.2.1",
|
|
51
|
+
"zod": "^3.24.2"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^22.13.10",
|
|
55
|
+
"typescript": "^5.8.2",
|
|
56
|
+
"vitest": "^3.0.9"
|
|
57
|
+
},
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|