conduit-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 +105 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +333 -0
- package/package.json +45 -0
- package/src/index.ts +359 -0
- package/tsconfig.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# conduit-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [Conduit](https://usecondu.it) — connect any AI agent to your data streams.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
Get an API key from [platform.usecondu.it/tokens](https://platform.usecondu.it/tokens), then add to your AI editor config:
|
|
8
|
+
|
|
9
|
+
### Claude Code / Claude Desktop
|
|
10
|
+
|
|
11
|
+
Add to `~/.claude/settings.json`:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"conduit": {
|
|
17
|
+
"command": "npx",
|
|
18
|
+
"args": ["-y", "conduit-mcp"],
|
|
19
|
+
"env": {
|
|
20
|
+
"CONDUIT_API_KEY": "conduit_sk_..."
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Cursor
|
|
28
|
+
|
|
29
|
+
Add to `.cursor/mcp.json` in your project:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"conduit": {
|
|
35
|
+
"command": "npx",
|
|
36
|
+
"args": ["-y", "conduit-mcp"],
|
|
37
|
+
"env": {
|
|
38
|
+
"CONDUIT_API_KEY": "conduit_sk_..."
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Windsurf
|
|
46
|
+
|
|
47
|
+
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"conduit": {
|
|
53
|
+
"command": "npx",
|
|
54
|
+
"args": ["-y", "conduit-mcp"],
|
|
55
|
+
"env": {
|
|
56
|
+
"CONDUIT_API_KEY": "conduit_sk_..."
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Environment Variables
|
|
64
|
+
|
|
65
|
+
| Variable | Required | Default | Description |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| `CONDUIT_API_KEY` | ✅ | — | Your Conduit API key |
|
|
68
|
+
| `CONDUIT_API_URL` | — | `https://api.usecondu.it` | Custom API endpoint |
|
|
69
|
+
|
|
70
|
+
## Tools
|
|
71
|
+
|
|
72
|
+
| Tool | Description |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `conduit_list_streams` | List all data streams |
|
|
75
|
+
| `conduit_get_schema` | Get schema for a stream (columns, types, codecs) |
|
|
76
|
+
| `conduit_create_stream` | Create a new stream |
|
|
77
|
+
| `conduit_ingest` | Send events to a stream |
|
|
78
|
+
| `conduit_list_events` | Query events with pagination & time range |
|
|
79
|
+
| `conduit_add_forward` | Add a webhook forwarding destination |
|
|
80
|
+
| `conduit_stream_stats` | Get ingestion statistics |
|
|
81
|
+
| `conduit_analyze_schema` | Analyze a JSON payload for optimal schema |
|
|
82
|
+
| `conduit_feedback` | Submit feedback to the Conduit team |
|
|
83
|
+
|
|
84
|
+
## Resources
|
|
85
|
+
|
|
86
|
+
| URI | Description |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `conduit://streams` | All streams |
|
|
89
|
+
| `conduit://streams/{name}` | Stream details + schema |
|
|
90
|
+
| `conduit://stats` | Platform-wide statistics |
|
|
91
|
+
|
|
92
|
+
## What is Conduit?
|
|
93
|
+
|
|
94
|
+
Conduit is the lightweight data layer between your services. Send any JSON — structure is inferred, not defined. Schema evolves automatically. Forward to any destination.
|
|
95
|
+
|
|
96
|
+
- **One endpoint, any protocol** — HTTP, WebSocket, MQTT
|
|
97
|
+
- **AI-powered schema detection** — zero configuration
|
|
98
|
+
- **Real-time forwarding** — webhooks, MQTT, more coming
|
|
99
|
+
- **Built for agents** — MCP-native from day one
|
|
100
|
+
|
|
101
|
+
Learn more at [usecondu.it](https://usecondu.it)
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Conduit MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Connect any AI agent to your Conduit data streams via
|
|
6
|
+
* the Model Context Protocol.
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* CONDUIT_API_URL — Conduit API base URL (default: https://api.usecondu.it)
|
|
10
|
+
* CONDUIT_API_KEY — Your Conduit API key (required)
|
|
11
|
+
*
|
|
12
|
+
* Tools:
|
|
13
|
+
* conduit_list_streams — List all streams
|
|
14
|
+
* conduit_get_schema — Get schema for a stream
|
|
15
|
+
* conduit_create_stream — Create a new stream
|
|
16
|
+
* conduit_ingest — Send events to a stream
|
|
17
|
+
* conduit_list_events — Query events with pagination & time range
|
|
18
|
+
* conduit_add_forward — Add a forwarding destination
|
|
19
|
+
* conduit_stream_stats — Get ingestion statistics
|
|
20
|
+
* conduit_analyze_schema — Analyze a JSON payload for optimal schema
|
|
21
|
+
* conduit_feedback — Submit feedback to the Conduit team
|
|
22
|
+
*
|
|
23
|
+
* Resources:
|
|
24
|
+
* conduit://streams — All streams
|
|
25
|
+
* conduit://streams/{name} — Stream details + schema
|
|
26
|
+
* conduit://stats — Platform-wide statistics
|
|
27
|
+
*/
|
|
28
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Conduit MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Connect any AI agent to your Conduit data streams via
|
|
6
|
+
* the Model Context Protocol.
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* CONDUIT_API_URL — Conduit API base URL (default: https://api.usecondu.it)
|
|
10
|
+
* CONDUIT_API_KEY — Your Conduit API key (required)
|
|
11
|
+
*
|
|
12
|
+
* Tools:
|
|
13
|
+
* conduit_list_streams — List all streams
|
|
14
|
+
* conduit_get_schema — Get schema for a stream
|
|
15
|
+
* conduit_create_stream — Create a new stream
|
|
16
|
+
* conduit_ingest — Send events to a stream
|
|
17
|
+
* conduit_list_events — Query events with pagination & time range
|
|
18
|
+
* conduit_add_forward — Add a forwarding destination
|
|
19
|
+
* conduit_stream_stats — Get ingestion statistics
|
|
20
|
+
* conduit_analyze_schema — Analyze a JSON payload for optimal schema
|
|
21
|
+
* conduit_feedback — Submit feedback to the Conduit team
|
|
22
|
+
*
|
|
23
|
+
* Resources:
|
|
24
|
+
* conduit://streams — All streams
|
|
25
|
+
* conduit://streams/{name} — Stream details + schema
|
|
26
|
+
* conduit://stats — Platform-wide statistics
|
|
27
|
+
*/
|
|
28
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
29
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
30
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
31
|
+
const API = process.env.CONDUIT_API_URL || 'https://api.usecondu.it';
|
|
32
|
+
const KEY = process.env.CONDUIT_API_KEY || '';
|
|
33
|
+
if (!KEY) {
|
|
34
|
+
console.error('[conduit-mcp] CONDUIT_API_KEY is required. Get one at https://platform.usecondu.it/tokens');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
async function api(path, options) {
|
|
38
|
+
const headers = {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
'Authorization': `Bearer ${KEY}`,
|
|
41
|
+
...(options?.headers || {}),
|
|
42
|
+
};
|
|
43
|
+
const res = await fetch(`${API}${path}`, { ...options, headers });
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
const body = await res.text().catch(() => '');
|
|
46
|
+
throw new Error(`API ${res.status}: ${body}`);
|
|
47
|
+
}
|
|
48
|
+
return res.json();
|
|
49
|
+
}
|
|
50
|
+
// Extract tenant prefix from first API call
|
|
51
|
+
let tenantPrefix = '';
|
|
52
|
+
async function getTenantPrefix() {
|
|
53
|
+
if (tenantPrefix)
|
|
54
|
+
return tenantPrefix;
|
|
55
|
+
// Get it from listing streams (the API key scopes to a tenant)
|
|
56
|
+
const streams = await api('/api/v1/streams');
|
|
57
|
+
if (Array.isArray(streams) && streams.length > 0) {
|
|
58
|
+
tenantPrefix = streams[0].tenant_id?.slice(0, 8) || 'unknown';
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
tenantPrefix = 'unknown';
|
|
62
|
+
}
|
|
63
|
+
return tenantPrefix;
|
|
64
|
+
}
|
|
65
|
+
const server = new Server({ name: 'conduit', version: '0.1.0' }, { capabilities: { tools: {}, resources: {} } });
|
|
66
|
+
// === TOOLS ===
|
|
67
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
68
|
+
tools: [
|
|
69
|
+
{
|
|
70
|
+
name: 'conduit_list_streams',
|
|
71
|
+
description: 'List all data streams in your Conduit account',
|
|
72
|
+
inputSchema: { type: 'object', properties: {} },
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'conduit_get_schema',
|
|
76
|
+
description: 'Get the current schema (columns, types, codecs) for a stream',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: { stream: { type: 'string', description: 'Stream name' } },
|
|
80
|
+
required: ['stream'],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'conduit_create_stream',
|
|
85
|
+
description: 'Create a new data stream. Just send a name — schema is auto-detected from the first event.',
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
name: { type: 'string', description: 'Stream name (alphanumeric, underscores, hyphens)' },
|
|
90
|
+
description: { type: 'string', description: 'Optional description' },
|
|
91
|
+
},
|
|
92
|
+
required: ['name'],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'conduit_ingest',
|
|
97
|
+
description: 'Send one or more events (JSON objects) to a stream. Schema is auto-detected and evolves.',
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
stream: { type: 'string', description: 'Stream name' },
|
|
102
|
+
events: {
|
|
103
|
+
oneOf: [
|
|
104
|
+
{ type: 'object', description: 'Single event' },
|
|
105
|
+
{ type: 'array', items: { type: 'object' }, description: 'Batch of events' },
|
|
106
|
+
],
|
|
107
|
+
description: 'Event(s) to ingest',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
required: ['stream', 'events'],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'conduit_list_events',
|
|
115
|
+
description: 'Query events from a stream with optional pagination and time range filters',
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
stream: { type: 'string', description: 'Stream name' },
|
|
120
|
+
limit: { type: 'number', description: 'Max events to return (default: 50, max: 1000)' },
|
|
121
|
+
offset: { type: 'number', description: 'Offset for pagination' },
|
|
122
|
+
from: { type: 'string', description: 'Start time (ISO 8601)' },
|
|
123
|
+
to: { type: 'string', description: 'End time (ISO 8601)' },
|
|
124
|
+
},
|
|
125
|
+
required: ['stream'],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'conduit_add_forward',
|
|
130
|
+
description: 'Add a forwarding destination to a stream. Supports HTTP webhooks, MQTT brokers, and WebSocket endpoints. Events are forwarded in real-time.',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {
|
|
134
|
+
stream: { type: 'string', description: 'Stream name' },
|
|
135
|
+
dest_type: { type: 'string', enum: ['http', 'mqtt', 'websocket'], description: 'Destination type (default: http)' },
|
|
136
|
+
url: { type: 'string', description: 'Webhook or WebSocket URL (for http/websocket types)' },
|
|
137
|
+
broker: { type: 'string', description: 'MQTT broker URL (for mqtt type)' },
|
|
138
|
+
topic: { type: 'string', description: 'MQTT topic (for mqtt type)' },
|
|
139
|
+
auth_method: { type: 'string', enum: ['none', 'bearer', 'hmac', 'api_key', 'basic', 'custom_headers'], description: 'Authentication method (default: none)' },
|
|
140
|
+
auth_token: { type: 'string', description: 'Bearer token (for bearer auth)' },
|
|
141
|
+
hmac_secret: { type: 'string', description: 'HMAC signing secret (for hmac auth)' },
|
|
142
|
+
basic_user: { type: 'string', description: 'Username (for basic auth)' },
|
|
143
|
+
basic_pass: { type: 'string', description: 'Password (for basic auth)' },
|
|
144
|
+
schema_mode: { type: 'string', enum: ['pass-through', 'pinned', 'mapped'], description: 'Schema mode (default: pass-through)' },
|
|
145
|
+
},
|
|
146
|
+
required: ['stream'],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'conduit_stream_stats',
|
|
151
|
+
description: 'Get ingestion statistics for a stream (event count, rate, schema version)',
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: { stream: { type: 'string', description: 'Stream name' } },
|
|
155
|
+
required: ['stream'],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'conduit_analyze_schema',
|
|
160
|
+
description: 'Analyze a JSON payload and get the optimal ClickHouse schema with compression codecs',
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: {
|
|
164
|
+
payload: { type: 'object', description: 'Sample JSON payload to analyze' },
|
|
165
|
+
},
|
|
166
|
+
required: ['payload'],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'conduit_feedback',
|
|
171
|
+
description: 'Submit feedback to the Conduit team — bugs, feature requests, improvements, or praise',
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: 'object',
|
|
174
|
+
properties: {
|
|
175
|
+
category: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
enum: ['bug', 'feature', 'improvement', 'general', 'praise'],
|
|
178
|
+
description: 'Feedback category',
|
|
179
|
+
},
|
|
180
|
+
message: { type: 'string', description: 'Your feedback (max 5000 chars)' },
|
|
181
|
+
context: { type: 'object', description: 'Optional context (stream name, error details, etc.)' },
|
|
182
|
+
},
|
|
183
|
+
required: ['category', 'message'],
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
}));
|
|
188
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
189
|
+
const { name, arguments: args } = request.params;
|
|
190
|
+
try {
|
|
191
|
+
switch (name) {
|
|
192
|
+
case 'conduit_list_streams': {
|
|
193
|
+
const streams = await api('/api/v1/streams');
|
|
194
|
+
return { content: [{ type: 'text', text: JSON.stringify(streams, null, 2) }] };
|
|
195
|
+
}
|
|
196
|
+
case 'conduit_get_schema': {
|
|
197
|
+
const data = await api(`/api/v1/streams/${args.stream}`);
|
|
198
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
199
|
+
}
|
|
200
|
+
case 'conduit_create_stream': {
|
|
201
|
+
const data = await api('/api/v1/streams', {
|
|
202
|
+
method: 'POST',
|
|
203
|
+
body: JSON.stringify({ name: args.name, description: args.description || '' }),
|
|
204
|
+
});
|
|
205
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
206
|
+
}
|
|
207
|
+
case 'conduit_ingest': {
|
|
208
|
+
const prefix = await getTenantPrefix();
|
|
209
|
+
const events = Array.isArray(args.events) ? args.events : [args.events];
|
|
210
|
+
const data = await api(`/v1/${prefix}/${args.stream}`, {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
body: JSON.stringify(events.length === 1 ? events[0] : events),
|
|
213
|
+
});
|
|
214
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
215
|
+
}
|
|
216
|
+
case 'conduit_list_events': {
|
|
217
|
+
const params = new URLSearchParams();
|
|
218
|
+
if (args.limit)
|
|
219
|
+
params.set('limit', String(args.limit));
|
|
220
|
+
if (args.offset)
|
|
221
|
+
params.set('offset', String(args.offset));
|
|
222
|
+
if (args.from)
|
|
223
|
+
params.set('from', args.from);
|
|
224
|
+
if (args.to)
|
|
225
|
+
params.set('to', args.to);
|
|
226
|
+
const qs = params.toString();
|
|
227
|
+
const data = await api(`/api/v1/streams/${args.stream}/events${qs ? `?${qs}` : ''}`);
|
|
228
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
229
|
+
}
|
|
230
|
+
case 'conduit_add_forward': {
|
|
231
|
+
const body = {
|
|
232
|
+
type: args.dest_type || 'http',
|
|
233
|
+
schema_mode: args.schema_mode || 'pass-through',
|
|
234
|
+
};
|
|
235
|
+
if (body.type === 'http' || body.type === 'websocket') {
|
|
236
|
+
body.url = args.url;
|
|
237
|
+
}
|
|
238
|
+
if (body.type === 'mqtt') {
|
|
239
|
+
body.broker = args.broker;
|
|
240
|
+
body.topic = args.topic;
|
|
241
|
+
if (args.mqtt_user)
|
|
242
|
+
body.mqtt_user = args.mqtt_user;
|
|
243
|
+
if (args.mqtt_pass)
|
|
244
|
+
body.mqtt_pass = args.mqtt_pass;
|
|
245
|
+
}
|
|
246
|
+
if (args.auth_method && args.auth_method !== 'none') {
|
|
247
|
+
body.auth_method = args.auth_method;
|
|
248
|
+
if (args.auth_token)
|
|
249
|
+
body.auth_token = args.auth_token;
|
|
250
|
+
if (args.hmac_secret)
|
|
251
|
+
body.hmac_secret = args.hmac_secret;
|
|
252
|
+
if (args.basic_user)
|
|
253
|
+
body.basic_user = args.basic_user;
|
|
254
|
+
if (args.basic_pass)
|
|
255
|
+
body.basic_pass = args.basic_pass;
|
|
256
|
+
}
|
|
257
|
+
const data = await api(`/api/v1/streams/${args.stream}/forwards`, {
|
|
258
|
+
method: 'POST',
|
|
259
|
+
body: JSON.stringify(body),
|
|
260
|
+
});
|
|
261
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
262
|
+
}
|
|
263
|
+
case 'conduit_stream_stats': {
|
|
264
|
+
const data = await api(`/api/v1/streams/${args.stream}/stats`);
|
|
265
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
266
|
+
}
|
|
267
|
+
case 'conduit_analyze_schema': {
|
|
268
|
+
const data = await api('/api/v1/analyze', {
|
|
269
|
+
method: 'POST',
|
|
270
|
+
body: JSON.stringify(args.payload),
|
|
271
|
+
});
|
|
272
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
273
|
+
}
|
|
274
|
+
case 'conduit_feedback': {
|
|
275
|
+
const data = await api('/api/v1/feedback', {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
body: JSON.stringify({
|
|
278
|
+
category: args.category,
|
|
279
|
+
message: args.message,
|
|
280
|
+
source: 'mcp',
|
|
281
|
+
context: args.context || {},
|
|
282
|
+
}),
|
|
283
|
+
});
|
|
284
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
285
|
+
}
|
|
286
|
+
default:
|
|
287
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
// === RESOURCES ===
|
|
295
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
296
|
+
resources: [
|
|
297
|
+
{ uri: 'conduit://streams', name: 'All Streams', description: 'List of all data streams', mimeType: 'application/json' },
|
|
298
|
+
{ uri: 'conduit://stats', name: 'Platform Stats', description: 'Platform-wide statistics', mimeType: 'application/json' },
|
|
299
|
+
],
|
|
300
|
+
}));
|
|
301
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
302
|
+
const { uri } = request.params;
|
|
303
|
+
if (uri === 'conduit://streams') {
|
|
304
|
+
const streams = await api('/api/v1/streams');
|
|
305
|
+
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(streams, null, 2) }] };
|
|
306
|
+
}
|
|
307
|
+
const streamMatch = uri.match(/^conduit:\/\/streams\/(.+)$/);
|
|
308
|
+
if (streamMatch) {
|
|
309
|
+
const data = await api(`/api/v1/streams/${streamMatch[1]}`);
|
|
310
|
+
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }] };
|
|
311
|
+
}
|
|
312
|
+
if (uri === 'conduit://stats') {
|
|
313
|
+
const streams = await api('/api/v1/streams');
|
|
314
|
+
return {
|
|
315
|
+
contents: [{
|
|
316
|
+
uri,
|
|
317
|
+
mimeType: 'application/json',
|
|
318
|
+
text: JSON.stringify({ totalStreams: Array.isArray(streams) ? streams.length : 0, streams }, null, 2),
|
|
319
|
+
}],
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
323
|
+
});
|
|
324
|
+
// === START ===
|
|
325
|
+
async function main() {
|
|
326
|
+
const transport = new StdioServerTransport();
|
|
327
|
+
await server.connect(transport);
|
|
328
|
+
console.error('[conduit-mcp] Connected — ready for requests');
|
|
329
|
+
}
|
|
330
|
+
main().catch((err) => {
|
|
331
|
+
console.error('[conduit-mcp] Fatal:', err);
|
|
332
|
+
process.exit(1);
|
|
333
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "conduit-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Conduit — connect any AI agent to your data streams",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"conduit-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsx src/index.ts",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"conduit",
|
|
18
|
+
"ai",
|
|
19
|
+
"data-pipeline",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"iot",
|
|
22
|
+
"streams"
|
|
23
|
+
],
|
|
24
|
+
"author": "Conduit <hello@usecondu.it>",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/useconduit/mcp"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://usecondu.it",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/useconduit/mcp/issues"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.27.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^25.3.3",
|
|
42
|
+
"tsx": "^4.19.0",
|
|
43
|
+
"typescript": "^5.7.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Conduit MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Connect any AI agent to your Conduit data streams via
|
|
7
|
+
* the Model Context Protocol.
|
|
8
|
+
*
|
|
9
|
+
* Environment variables:
|
|
10
|
+
* CONDUIT_API_URL — Conduit API base URL (default: https://api.usecondu.it)
|
|
11
|
+
* CONDUIT_API_KEY — Your Conduit API key (required)
|
|
12
|
+
*
|
|
13
|
+
* Tools:
|
|
14
|
+
* conduit_list_streams — List all streams
|
|
15
|
+
* conduit_get_schema — Get schema for a stream
|
|
16
|
+
* conduit_create_stream — Create a new stream
|
|
17
|
+
* conduit_ingest — Send events to a stream
|
|
18
|
+
* conduit_list_events — Query events with pagination & time range
|
|
19
|
+
* conduit_add_forward — Add a forwarding destination
|
|
20
|
+
* conduit_stream_stats — Get ingestion statistics
|
|
21
|
+
* conduit_analyze_schema — Analyze a JSON payload for optimal schema
|
|
22
|
+
* conduit_feedback — Submit feedback to the Conduit team
|
|
23
|
+
*
|
|
24
|
+
* Resources:
|
|
25
|
+
* conduit://streams — All streams
|
|
26
|
+
* conduit://streams/{name} — Stream details + schema
|
|
27
|
+
* conduit://stats — Platform-wide statistics
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
31
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
32
|
+
import {
|
|
33
|
+
CallToolRequestSchema,
|
|
34
|
+
ListToolsRequestSchema,
|
|
35
|
+
ListResourcesRequestSchema,
|
|
36
|
+
ReadResourceRequestSchema,
|
|
37
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
38
|
+
|
|
39
|
+
const API = process.env.CONDUIT_API_URL || 'https://api.usecondu.it';
|
|
40
|
+
const KEY = process.env.CONDUIT_API_KEY || '';
|
|
41
|
+
|
|
42
|
+
if (!KEY) {
|
|
43
|
+
console.error('[conduit-mcp] CONDUIT_API_KEY is required. Get one at https://platform.usecondu.it/tokens');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function api(path: string, options?: RequestInit) {
|
|
48
|
+
const headers: Record<string, string> = {
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
'Authorization': `Bearer ${KEY}`,
|
|
51
|
+
...(options?.headers as Record<string, string> || {}),
|
|
52
|
+
};
|
|
53
|
+
const res = await fetch(`${API}${path}`, { ...options, headers });
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
const body = await res.text().catch(() => '');
|
|
56
|
+
throw new Error(`API ${res.status}: ${body}`);
|
|
57
|
+
}
|
|
58
|
+
return res.json();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Extract tenant prefix from first API call
|
|
62
|
+
let tenantPrefix = '';
|
|
63
|
+
|
|
64
|
+
async function getTenantPrefix(): Promise<string> {
|
|
65
|
+
if (tenantPrefix) return tenantPrefix;
|
|
66
|
+
// Get it from listing streams (the API key scopes to a tenant)
|
|
67
|
+
const streams = await api('/api/v1/streams');
|
|
68
|
+
if (Array.isArray(streams) && streams.length > 0) {
|
|
69
|
+
tenantPrefix = (streams[0] as any).tenant_id?.slice(0, 8) || 'unknown';
|
|
70
|
+
} else {
|
|
71
|
+
tenantPrefix = 'unknown';
|
|
72
|
+
}
|
|
73
|
+
return tenantPrefix;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const server = new Server(
|
|
77
|
+
{ name: 'conduit', version: '0.1.0' },
|
|
78
|
+
{ capabilities: { tools: {}, resources: {} } }
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// === TOOLS ===
|
|
82
|
+
|
|
83
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
84
|
+
tools: [
|
|
85
|
+
{
|
|
86
|
+
name: 'conduit_list_streams',
|
|
87
|
+
description: 'List all data streams in your Conduit account',
|
|
88
|
+
inputSchema: { type: 'object' as const, properties: {} },
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'conduit_get_schema',
|
|
92
|
+
description: 'Get the current schema (columns, types, codecs) for a stream',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object' as const,
|
|
95
|
+
properties: { stream: { type: 'string', description: 'Stream name' } },
|
|
96
|
+
required: ['stream'],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'conduit_create_stream',
|
|
101
|
+
description: 'Create a new data stream. Just send a name — schema is auto-detected from the first event.',
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: 'object' as const,
|
|
104
|
+
properties: {
|
|
105
|
+
name: { type: 'string', description: 'Stream name (alphanumeric, underscores, hyphens)' },
|
|
106
|
+
description: { type: 'string', description: 'Optional description' },
|
|
107
|
+
},
|
|
108
|
+
required: ['name'],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'conduit_ingest',
|
|
113
|
+
description: 'Send one or more events (JSON objects) to a stream. Schema is auto-detected and evolves.',
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: 'object' as const,
|
|
116
|
+
properties: {
|
|
117
|
+
stream: { type: 'string', description: 'Stream name' },
|
|
118
|
+
events: {
|
|
119
|
+
oneOf: [
|
|
120
|
+
{ type: 'object', description: 'Single event' },
|
|
121
|
+
{ type: 'array', items: { type: 'object' }, description: 'Batch of events' },
|
|
122
|
+
],
|
|
123
|
+
description: 'Event(s) to ingest',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
required: ['stream', 'events'],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'conduit_list_events',
|
|
131
|
+
description: 'Query events from a stream with optional pagination and time range filters',
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: 'object' as const,
|
|
134
|
+
properties: {
|
|
135
|
+
stream: { type: 'string', description: 'Stream name' },
|
|
136
|
+
limit: { type: 'number', description: 'Max events to return (default: 50, max: 1000)' },
|
|
137
|
+
offset: { type: 'number', description: 'Offset for pagination' },
|
|
138
|
+
from: { type: 'string', description: 'Start time (ISO 8601)' },
|
|
139
|
+
to: { type: 'string', description: 'End time (ISO 8601)' },
|
|
140
|
+
},
|
|
141
|
+
required: ['stream'],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'conduit_add_forward',
|
|
146
|
+
description: 'Add a forwarding destination to a stream. Supports HTTP webhooks, MQTT brokers, and WebSocket endpoints. Events are forwarded in real-time.',
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: 'object' as const,
|
|
149
|
+
properties: {
|
|
150
|
+
stream: { type: 'string', description: 'Stream name' },
|
|
151
|
+
dest_type: { type: 'string', enum: ['http', 'mqtt', 'websocket'], description: 'Destination type (default: http)' },
|
|
152
|
+
url: { type: 'string', description: 'Webhook or WebSocket URL (for http/websocket types)' },
|
|
153
|
+
broker: { type: 'string', description: 'MQTT broker URL (for mqtt type)' },
|
|
154
|
+
topic: { type: 'string', description: 'MQTT topic (for mqtt type)' },
|
|
155
|
+
auth_method: { type: 'string', enum: ['none', 'bearer', 'hmac', 'api_key', 'basic', 'custom_headers'], description: 'Authentication method (default: none)' },
|
|
156
|
+
auth_token: { type: 'string', description: 'Bearer token (for bearer auth)' },
|
|
157
|
+
hmac_secret: { type: 'string', description: 'HMAC signing secret (for hmac auth)' },
|
|
158
|
+
basic_user: { type: 'string', description: 'Username (for basic auth)' },
|
|
159
|
+
basic_pass: { type: 'string', description: 'Password (for basic auth)' },
|
|
160
|
+
schema_mode: { type: 'string', enum: ['pass-through', 'pinned', 'mapped'], description: 'Schema mode (default: pass-through)' },
|
|
161
|
+
},
|
|
162
|
+
required: ['stream'],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'conduit_stream_stats',
|
|
167
|
+
description: 'Get ingestion statistics for a stream (event count, rate, schema version)',
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: 'object' as const,
|
|
170
|
+
properties: { stream: { type: 'string', description: 'Stream name' } },
|
|
171
|
+
required: ['stream'],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'conduit_analyze_schema',
|
|
176
|
+
description: 'Analyze a JSON payload and get the optimal ClickHouse schema with compression codecs',
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: 'object' as const,
|
|
179
|
+
properties: {
|
|
180
|
+
payload: { type: 'object', description: 'Sample JSON payload to analyze' },
|
|
181
|
+
},
|
|
182
|
+
required: ['payload'],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'conduit_feedback',
|
|
187
|
+
description: 'Submit feedback to the Conduit team — bugs, feature requests, improvements, or praise',
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: 'object' as const,
|
|
190
|
+
properties: {
|
|
191
|
+
category: {
|
|
192
|
+
type: 'string',
|
|
193
|
+
enum: ['bug', 'feature', 'improvement', 'general', 'praise'],
|
|
194
|
+
description: 'Feedback category',
|
|
195
|
+
},
|
|
196
|
+
message: { type: 'string', description: 'Your feedback (max 5000 chars)' },
|
|
197
|
+
context: { type: 'object', description: 'Optional context (stream name, error details, etc.)' },
|
|
198
|
+
},
|
|
199
|
+
required: ['category', 'message'],
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
}));
|
|
204
|
+
|
|
205
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
206
|
+
const { name, arguments: args } = request.params;
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
switch (name) {
|
|
210
|
+
case 'conduit_list_streams': {
|
|
211
|
+
const streams = await api('/api/v1/streams');
|
|
212
|
+
return { content: [{ type: 'text', text: JSON.stringify(streams, null, 2) }] };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
case 'conduit_get_schema': {
|
|
216
|
+
const data = await api(`/api/v1/streams/${args!.stream}`);
|
|
217
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case 'conduit_create_stream': {
|
|
221
|
+
const data = await api('/api/v1/streams', {
|
|
222
|
+
method: 'POST',
|
|
223
|
+
body: JSON.stringify({ name: args!.name, description: args!.description || '' }),
|
|
224
|
+
});
|
|
225
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
case 'conduit_ingest': {
|
|
229
|
+
const prefix = await getTenantPrefix();
|
|
230
|
+
const events = Array.isArray(args!.events) ? args!.events : [args!.events];
|
|
231
|
+
const data = await api(`/v1/${prefix}/${args!.stream}`, {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
body: JSON.stringify(events.length === 1 ? events[0] : events),
|
|
234
|
+
});
|
|
235
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
case 'conduit_list_events': {
|
|
239
|
+
const params = new URLSearchParams();
|
|
240
|
+
if (args!.limit) params.set('limit', String(args!.limit));
|
|
241
|
+
if (args!.offset) params.set('offset', String(args!.offset));
|
|
242
|
+
if (args!.from) params.set('from', args!.from as string);
|
|
243
|
+
if (args!.to) params.set('to', args!.to as string);
|
|
244
|
+
const qs = params.toString();
|
|
245
|
+
const data = await api(`/api/v1/streams/${args!.stream}/events${qs ? `?${qs}` : ''}`);
|
|
246
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
case 'conduit_add_forward': {
|
|
250
|
+
const body: Record<string, any> = {
|
|
251
|
+
type: args!.dest_type || 'http',
|
|
252
|
+
schema_mode: args!.schema_mode || 'pass-through',
|
|
253
|
+
};
|
|
254
|
+
if (body.type === 'http' || body.type === 'websocket') {
|
|
255
|
+
body.url = args!.url;
|
|
256
|
+
}
|
|
257
|
+
if (body.type === 'mqtt') {
|
|
258
|
+
body.broker = args!.broker;
|
|
259
|
+
body.topic = args!.topic;
|
|
260
|
+
if (args!.mqtt_user) body.mqtt_user = args!.mqtt_user;
|
|
261
|
+
if (args!.mqtt_pass) body.mqtt_pass = args!.mqtt_pass;
|
|
262
|
+
}
|
|
263
|
+
if (args!.auth_method && args!.auth_method !== 'none') {
|
|
264
|
+
body.auth_method = args!.auth_method;
|
|
265
|
+
if (args!.auth_token) body.auth_token = args!.auth_token;
|
|
266
|
+
if (args!.hmac_secret) body.hmac_secret = args!.hmac_secret;
|
|
267
|
+
if (args!.basic_user) body.basic_user = args!.basic_user;
|
|
268
|
+
if (args!.basic_pass) body.basic_pass = args!.basic_pass;
|
|
269
|
+
}
|
|
270
|
+
const data = await api(`/api/v1/streams/${args!.stream}/forwards`, {
|
|
271
|
+
method: 'POST',
|
|
272
|
+
body: JSON.stringify(body),
|
|
273
|
+
});
|
|
274
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
case 'conduit_stream_stats': {
|
|
278
|
+
const data = await api(`/api/v1/streams/${args!.stream}/stats`);
|
|
279
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
case 'conduit_analyze_schema': {
|
|
283
|
+
const data = await api('/api/v1/analyze', {
|
|
284
|
+
method: 'POST',
|
|
285
|
+
body: JSON.stringify(args!.payload),
|
|
286
|
+
});
|
|
287
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
case 'conduit_feedback': {
|
|
291
|
+
const data = await api('/api/v1/feedback', {
|
|
292
|
+
method: 'POST',
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
category: args!.category,
|
|
295
|
+
message: args!.message,
|
|
296
|
+
source: 'mcp',
|
|
297
|
+
context: args!.context || {},
|
|
298
|
+
}),
|
|
299
|
+
});
|
|
300
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
default:
|
|
304
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
305
|
+
}
|
|
306
|
+
} catch (err: any) {
|
|
307
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// === RESOURCES ===
|
|
312
|
+
|
|
313
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
314
|
+
resources: [
|
|
315
|
+
{ uri: 'conduit://streams', name: 'All Streams', description: 'List of all data streams', mimeType: 'application/json' },
|
|
316
|
+
{ uri: 'conduit://stats', name: 'Platform Stats', description: 'Platform-wide statistics', mimeType: 'application/json' },
|
|
317
|
+
],
|
|
318
|
+
}));
|
|
319
|
+
|
|
320
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
321
|
+
const { uri } = request.params;
|
|
322
|
+
|
|
323
|
+
if (uri === 'conduit://streams') {
|
|
324
|
+
const streams = await api('/api/v1/streams');
|
|
325
|
+
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(streams, null, 2) }] };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const streamMatch = uri.match(/^conduit:\/\/streams\/(.+)$/);
|
|
329
|
+
if (streamMatch) {
|
|
330
|
+
const data = await api(`/api/v1/streams/${streamMatch[1]}`);
|
|
331
|
+
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }] };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (uri === 'conduit://stats') {
|
|
335
|
+
const streams = await api('/api/v1/streams');
|
|
336
|
+
return {
|
|
337
|
+
contents: [{
|
|
338
|
+
uri,
|
|
339
|
+
mimeType: 'application/json',
|
|
340
|
+
text: JSON.stringify({ totalStreams: Array.isArray(streams) ? streams.length : 0, streams }, null, 2),
|
|
341
|
+
}],
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// === START ===
|
|
349
|
+
|
|
350
|
+
async function main() {
|
|
351
|
+
const transport = new StdioServerTransport();
|
|
352
|
+
await server.connect(transport);
|
|
353
|
+
console.error('[conduit-mcp] Connected — ready for requests');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
main().catch((err) => {
|
|
357
|
+
console.error('[conduit-mcp] Fatal:', err);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|