@contextstream/mcp-server 0.2.6 → 0.2.7
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 +11 -23
- package/package.json +1 -1
- package/.env.example +0 -5
- package/src/client.ts +0 -764
- package/src/config.ts +0 -36
- package/src/files.ts +0 -261
- package/src/http.ts +0 -154
- package/src/index.ts +0 -38
- package/src/prompts.ts +0 -308
- package/src/resources.ts +0 -49
- package/src/tools.ts +0 -1040
- package/src/workspace-config.ts +0 -150
- package/tsconfig.json +0 -14
package/src/config.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
const configSchema = z.object({
|
|
4
|
-
apiUrl: z.string().url(),
|
|
5
|
-
apiKey: z.string().min(1).optional(),
|
|
6
|
-
jwt: z.string().min(1).optional(),
|
|
7
|
-
defaultWorkspaceId: z.string().uuid().optional(),
|
|
8
|
-
defaultProjectId: z.string().uuid().optional(),
|
|
9
|
-
userAgent: z.string().default('contextstream-mcp/0.1.0'),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
export type Config = z.infer<typeof configSchema>;
|
|
13
|
-
|
|
14
|
-
export function loadConfig(): Config {
|
|
15
|
-
const parsed = configSchema.safeParse({
|
|
16
|
-
apiUrl: process.env.CONTEXTSTREAM_API_URL,
|
|
17
|
-
apiKey: process.env.CONTEXTSTREAM_API_KEY,
|
|
18
|
-
jwt: process.env.CONTEXTSTREAM_JWT,
|
|
19
|
-
defaultWorkspaceId: process.env.CONTEXTSTREAM_WORKSPACE_ID,
|
|
20
|
-
defaultProjectId: process.env.CONTEXTSTREAM_PROJECT_ID,
|
|
21
|
-
userAgent: process.env.CONTEXTSTREAM_USER_AGENT,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
if (!parsed.success) {
|
|
25
|
-
const missing = parsed.error.errors.map((e) => e.path.join('.')).join(', ');
|
|
26
|
-
throw new Error(
|
|
27
|
-
`Invalid configuration. Set CONTEXTSTREAM_API_URL (and API key or JWT). Missing/invalid: ${missing}`
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!parsed.data.apiKey && !parsed.data.jwt) {
|
|
32
|
-
throw new Error('Set CONTEXTSTREAM_API_KEY or CONTEXTSTREAM_JWT for authentication.');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return parsed.data;
|
|
36
|
-
}
|
package/src/files.ts
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File reading utilities for code indexing
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
|
|
8
|
-
export interface FileToIngest {
|
|
9
|
-
path: string;
|
|
10
|
-
content: string;
|
|
11
|
-
language?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// File extensions to include for indexing
|
|
15
|
-
const CODE_EXTENSIONS = new Set([
|
|
16
|
-
// Rust
|
|
17
|
-
'rs',
|
|
18
|
-
// TypeScript/JavaScript
|
|
19
|
-
'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',
|
|
20
|
-
// Python
|
|
21
|
-
'py', 'pyi',
|
|
22
|
-
// Go
|
|
23
|
-
'go',
|
|
24
|
-
// Java/Kotlin
|
|
25
|
-
'java', 'kt', 'kts',
|
|
26
|
-
// C/C++
|
|
27
|
-
'c', 'h', 'cpp', 'hpp', 'cc', 'cxx',
|
|
28
|
-
// C#
|
|
29
|
-
'cs',
|
|
30
|
-
// Ruby
|
|
31
|
-
'rb',
|
|
32
|
-
// PHP
|
|
33
|
-
'php',
|
|
34
|
-
// Swift
|
|
35
|
-
'swift',
|
|
36
|
-
// Scala
|
|
37
|
-
'scala',
|
|
38
|
-
// Shell
|
|
39
|
-
'sh', 'bash', 'zsh',
|
|
40
|
-
// Config/Data
|
|
41
|
-
'json', 'yaml', 'yml', 'toml', 'xml',
|
|
42
|
-
// SQL
|
|
43
|
-
'sql',
|
|
44
|
-
// Markdown/Docs
|
|
45
|
-
'md', 'markdown', 'rst', 'txt',
|
|
46
|
-
// HTML/CSS
|
|
47
|
-
'html', 'htm', 'css', 'scss', 'sass', 'less',
|
|
48
|
-
// Other
|
|
49
|
-
'graphql', 'proto', 'dockerfile',
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
// Directories to ignore
|
|
53
|
-
const IGNORE_DIRS = new Set([
|
|
54
|
-
'node_modules',
|
|
55
|
-
'.git',
|
|
56
|
-
'.svn',
|
|
57
|
-
'.hg',
|
|
58
|
-
'target',
|
|
59
|
-
'dist',
|
|
60
|
-
'build',
|
|
61
|
-
'out',
|
|
62
|
-
'.next',
|
|
63
|
-
'.nuxt',
|
|
64
|
-
'__pycache__',
|
|
65
|
-
'.pytest_cache',
|
|
66
|
-
'.mypy_cache',
|
|
67
|
-
'venv',
|
|
68
|
-
'.venv',
|
|
69
|
-
'env',
|
|
70
|
-
'.env',
|
|
71
|
-
'vendor',
|
|
72
|
-
'coverage',
|
|
73
|
-
'.coverage',
|
|
74
|
-
'.idea',
|
|
75
|
-
'.vscode',
|
|
76
|
-
'.vs',
|
|
77
|
-
]);
|
|
78
|
-
|
|
79
|
-
// Files to ignore
|
|
80
|
-
const IGNORE_FILES = new Set([
|
|
81
|
-
'.DS_Store',
|
|
82
|
-
'Thumbs.db',
|
|
83
|
-
'.gitignore',
|
|
84
|
-
'.gitattributes',
|
|
85
|
-
'package-lock.json',
|
|
86
|
-
'yarn.lock',
|
|
87
|
-
'pnpm-lock.yaml',
|
|
88
|
-
'Cargo.lock',
|
|
89
|
-
'poetry.lock',
|
|
90
|
-
'Gemfile.lock',
|
|
91
|
-
'composer.lock',
|
|
92
|
-
]);
|
|
93
|
-
|
|
94
|
-
// Max file size to index (1MB)
|
|
95
|
-
const MAX_FILE_SIZE = 1024 * 1024;
|
|
96
|
-
|
|
97
|
-
// Max number of files to index in one batch
|
|
98
|
-
const MAX_FILES_PER_BATCH = 100;
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Read all indexable files from a directory
|
|
102
|
-
*/
|
|
103
|
-
export async function readFilesFromDirectory(
|
|
104
|
-
rootPath: string,
|
|
105
|
-
options: {
|
|
106
|
-
maxFiles?: number;
|
|
107
|
-
maxFileSize?: number;
|
|
108
|
-
} = {}
|
|
109
|
-
): Promise<FileToIngest[]> {
|
|
110
|
-
const maxFiles = options.maxFiles ?? MAX_FILES_PER_BATCH;
|
|
111
|
-
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
112
|
-
const files: FileToIngest[] = [];
|
|
113
|
-
|
|
114
|
-
async function walkDir(dir: string, relativePath: string = ''): Promise<void> {
|
|
115
|
-
if (files.length >= maxFiles) return;
|
|
116
|
-
|
|
117
|
-
let entries: fs.Dirent[];
|
|
118
|
-
try {
|
|
119
|
-
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
120
|
-
} catch {
|
|
121
|
-
return; // Skip directories we can't read
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
for (const entry of entries) {
|
|
125
|
-
if (files.length >= maxFiles) break;
|
|
126
|
-
|
|
127
|
-
const fullPath = path.join(dir, entry.name);
|
|
128
|
-
const relPath = path.join(relativePath, entry.name);
|
|
129
|
-
|
|
130
|
-
if (entry.isDirectory()) {
|
|
131
|
-
// Skip ignored directories
|
|
132
|
-
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
133
|
-
await walkDir(fullPath, relPath);
|
|
134
|
-
} else if (entry.isFile()) {
|
|
135
|
-
// Skip ignored files
|
|
136
|
-
if (IGNORE_FILES.has(entry.name)) continue;
|
|
137
|
-
|
|
138
|
-
// Check extension
|
|
139
|
-
const ext = entry.name.split('.').pop()?.toLowerCase() ?? '';
|
|
140
|
-
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
141
|
-
|
|
142
|
-
// Check file size
|
|
143
|
-
try {
|
|
144
|
-
const stat = await fs.promises.stat(fullPath);
|
|
145
|
-
if (stat.size > maxFileSize) continue;
|
|
146
|
-
|
|
147
|
-
// Read file content
|
|
148
|
-
const content = await fs.promises.readFile(fullPath, 'utf-8');
|
|
149
|
-
files.push({
|
|
150
|
-
path: relPath,
|
|
151
|
-
content,
|
|
152
|
-
});
|
|
153
|
-
} catch {
|
|
154
|
-
// Skip files we can't read
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
await walkDir(rootPath);
|
|
161
|
-
return files;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Read ALL indexable files from a directory (no limit)
|
|
166
|
-
* Returns files in batches via async generator for memory efficiency
|
|
167
|
-
*/
|
|
168
|
-
export async function* readAllFilesInBatches(
|
|
169
|
-
rootPath: string,
|
|
170
|
-
options: {
|
|
171
|
-
batchSize?: number;
|
|
172
|
-
maxFileSize?: number;
|
|
173
|
-
} = {}
|
|
174
|
-
): AsyncGenerator<FileToIngest[], void, unknown> {
|
|
175
|
-
const batchSize = options.batchSize ?? 50;
|
|
176
|
-
const maxFileSize = options.maxFileSize ?? MAX_FILE_SIZE;
|
|
177
|
-
let batch: FileToIngest[] = [];
|
|
178
|
-
|
|
179
|
-
async function* walkDir(dir: string, relativePath: string = ''): AsyncGenerator<FileToIngest, void, unknown> {
|
|
180
|
-
let entries: fs.Dirent[];
|
|
181
|
-
try {
|
|
182
|
-
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
183
|
-
} catch {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
for (const entry of entries) {
|
|
188
|
-
const fullPath = path.join(dir, entry.name);
|
|
189
|
-
const relPath = path.join(relativePath, entry.name);
|
|
190
|
-
|
|
191
|
-
if (entry.isDirectory()) {
|
|
192
|
-
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
193
|
-
yield* walkDir(fullPath, relPath);
|
|
194
|
-
} else if (entry.isFile()) {
|
|
195
|
-
if (IGNORE_FILES.has(entry.name)) continue;
|
|
196
|
-
|
|
197
|
-
const ext = entry.name.split('.').pop()?.toLowerCase() ?? '';
|
|
198
|
-
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
const stat = await fs.promises.stat(fullPath);
|
|
202
|
-
if (stat.size > maxFileSize) continue;
|
|
203
|
-
|
|
204
|
-
const content = await fs.promises.readFile(fullPath, 'utf-8');
|
|
205
|
-
yield { path: relPath, content };
|
|
206
|
-
} catch {
|
|
207
|
-
// Skip unreadable files
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
for await (const file of walkDir(rootPath)) {
|
|
214
|
-
batch.push(file);
|
|
215
|
-
if (batch.length >= batchSize) {
|
|
216
|
-
yield batch;
|
|
217
|
-
batch = [];
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (batch.length > 0) {
|
|
222
|
-
yield batch;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Get language from file extension
|
|
228
|
-
*/
|
|
229
|
-
export function detectLanguage(filePath: string): string {
|
|
230
|
-
const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
|
|
231
|
-
const langMap: Record<string, string> = {
|
|
232
|
-
rs: 'rust',
|
|
233
|
-
ts: 'typescript',
|
|
234
|
-
tsx: 'typescript',
|
|
235
|
-
js: 'javascript',
|
|
236
|
-
jsx: 'javascript',
|
|
237
|
-
py: 'python',
|
|
238
|
-
go: 'go',
|
|
239
|
-
java: 'java',
|
|
240
|
-
kt: 'kotlin',
|
|
241
|
-
c: 'c',
|
|
242
|
-
h: 'c',
|
|
243
|
-
cpp: 'cpp',
|
|
244
|
-
hpp: 'cpp',
|
|
245
|
-
cs: 'csharp',
|
|
246
|
-
rb: 'ruby',
|
|
247
|
-
php: 'php',
|
|
248
|
-
swift: 'swift',
|
|
249
|
-
scala: 'scala',
|
|
250
|
-
sql: 'sql',
|
|
251
|
-
md: 'markdown',
|
|
252
|
-
json: 'json',
|
|
253
|
-
yaml: 'yaml',
|
|
254
|
-
yml: 'yaml',
|
|
255
|
-
toml: 'toml',
|
|
256
|
-
html: 'html',
|
|
257
|
-
css: 'css',
|
|
258
|
-
sh: 'shell',
|
|
259
|
-
};
|
|
260
|
-
return langMap[ext] ?? 'unknown';
|
|
261
|
-
}
|
package/src/http.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import type { Config } from './config.js';
|
|
2
|
-
|
|
3
|
-
export class HttpError extends Error {
|
|
4
|
-
status: number;
|
|
5
|
-
body: any;
|
|
6
|
-
code: string;
|
|
7
|
-
|
|
8
|
-
constructor(status: number, message: string, body?: any) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.name = 'HttpError';
|
|
11
|
-
this.status = status;
|
|
12
|
-
this.body = body;
|
|
13
|
-
this.code = statusToCode(status);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
toJSON() {
|
|
17
|
-
return {
|
|
18
|
-
error: this.message,
|
|
19
|
-
status: this.status,
|
|
20
|
-
code: this.code,
|
|
21
|
-
details: this.body,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function statusToCode(status: number): string {
|
|
27
|
-
switch (status) {
|
|
28
|
-
case 0: return 'NETWORK_ERROR';
|
|
29
|
-
case 400: return 'BAD_REQUEST';
|
|
30
|
-
case 401: return 'UNAUTHORIZED';
|
|
31
|
-
case 403: return 'FORBIDDEN';
|
|
32
|
-
case 404: return 'NOT_FOUND';
|
|
33
|
-
case 409: return 'CONFLICT';
|
|
34
|
-
case 422: return 'VALIDATION_ERROR';
|
|
35
|
-
case 429: return 'RATE_LIMITED';
|
|
36
|
-
case 500: return 'INTERNAL_ERROR';
|
|
37
|
-
case 502: return 'BAD_GATEWAY';
|
|
38
|
-
case 503: return 'SERVICE_UNAVAILABLE';
|
|
39
|
-
case 504: return 'GATEWAY_TIMEOUT';
|
|
40
|
-
default: return 'UNKNOWN_ERROR';
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface RequestOptions {
|
|
45
|
-
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
46
|
-
body?: any;
|
|
47
|
-
signal?: AbortSignal;
|
|
48
|
-
retries?: number;
|
|
49
|
-
retryDelay?: number;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
|
|
53
|
-
const MAX_RETRIES = 3;
|
|
54
|
-
const BASE_DELAY = 1000;
|
|
55
|
-
|
|
56
|
-
async function sleep(ms: number): Promise<void> {
|
|
57
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function request<T>(
|
|
61
|
-
config: Config,
|
|
62
|
-
path: string,
|
|
63
|
-
options: RequestOptions = {}
|
|
64
|
-
): Promise<T> {
|
|
65
|
-
const { apiUrl, apiKey, jwt, userAgent } = config;
|
|
66
|
-
// Ensure path has /api/v1 prefix
|
|
67
|
-
const apiPath = path.startsWith('/api/') ? path : `/api/v1${path}`;
|
|
68
|
-
const url = `${apiUrl.replace(/\/$/, '')}${apiPath}`;
|
|
69
|
-
const maxRetries = options.retries ?? MAX_RETRIES;
|
|
70
|
-
const baseDelay = options.retryDelay ?? BASE_DELAY;
|
|
71
|
-
|
|
72
|
-
const headers: Record<string, string> = {
|
|
73
|
-
'Content-Type': 'application/json',
|
|
74
|
-
'User-Agent': userAgent,
|
|
75
|
-
};
|
|
76
|
-
if (apiKey) headers['X-API-Key'] = apiKey;
|
|
77
|
-
if (jwt) headers['Authorization'] = `Bearer ${jwt}`;
|
|
78
|
-
|
|
79
|
-
const fetchOptions: RequestInit = {
|
|
80
|
-
method: options.method || (options.body ? 'POST' : 'GET'),
|
|
81
|
-
headers,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
if (options.body !== undefined) {
|
|
85
|
-
fetchOptions.body = JSON.stringify(options.body);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let lastError: HttpError | null = null;
|
|
89
|
-
|
|
90
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
91
|
-
const controller = new AbortController();
|
|
92
|
-
const timeout = setTimeout(() => controller.abort(), 180_000);
|
|
93
|
-
|
|
94
|
-
// Combine user signal with timeout
|
|
95
|
-
if (options.signal) {
|
|
96
|
-
options.signal.addEventListener('abort', () => controller.abort());
|
|
97
|
-
}
|
|
98
|
-
fetchOptions.signal = controller.signal;
|
|
99
|
-
|
|
100
|
-
let response: Response;
|
|
101
|
-
try {
|
|
102
|
-
response = await fetch(url, fetchOptions);
|
|
103
|
-
} catch (error: any) {
|
|
104
|
-
clearTimeout(timeout);
|
|
105
|
-
|
|
106
|
-
// Handle abort
|
|
107
|
-
if (error.name === 'AbortError') {
|
|
108
|
-
if (options.signal?.aborted) {
|
|
109
|
-
throw new HttpError(0, 'Request cancelled by user');
|
|
110
|
-
}
|
|
111
|
-
throw new HttpError(0, 'Request timeout after 180 seconds');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
lastError = new HttpError(0, error?.message || 'Network error');
|
|
115
|
-
|
|
116
|
-
// Retry on network errors
|
|
117
|
-
if (attempt < maxRetries) {
|
|
118
|
-
const delay = baseDelay * Math.pow(2, attempt);
|
|
119
|
-
await sleep(delay);
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
throw lastError;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
clearTimeout(timeout);
|
|
126
|
-
|
|
127
|
-
let payload: any = null;
|
|
128
|
-
const contentType = response.headers.get('content-type') || '';
|
|
129
|
-
if (contentType.includes('application/json')) {
|
|
130
|
-
payload = await response.json().catch(() => null);
|
|
131
|
-
} else {
|
|
132
|
-
payload = await response.text().catch(() => null);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (!response.ok) {
|
|
136
|
-
const message = payload?.message || payload?.error || response.statusText;
|
|
137
|
-
lastError = new HttpError(response.status, message, payload);
|
|
138
|
-
|
|
139
|
-
// Retry on retryable status codes
|
|
140
|
-
if (RETRYABLE_STATUSES.has(response.status) && attempt < maxRetries) {
|
|
141
|
-
const retryAfter = response.headers.get('retry-after');
|
|
142
|
-
const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : baseDelay * Math.pow(2, attempt);
|
|
143
|
-
await sleep(delay);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
throw lastError;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return payload as T;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
throw lastError || new HttpError(0, 'Request failed after retries');
|
|
154
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { loadConfig } from './config.js';
|
|
4
|
-
import { ContextStreamClient } from './client.js';
|
|
5
|
-
import { registerTools } from './tools.js';
|
|
6
|
-
import { registerResources } from './resources.js';
|
|
7
|
-
import { registerPrompts } from './prompts.js';
|
|
8
|
-
|
|
9
|
-
async function main() {
|
|
10
|
-
const config = loadConfig();
|
|
11
|
-
const client = new ContextStreamClient(config);
|
|
12
|
-
|
|
13
|
-
const server = new McpServer({
|
|
14
|
-
name: 'contextstream-mcp',
|
|
15
|
-
version: '0.2.0',
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// Register all MCP components
|
|
19
|
-
registerTools(server, client);
|
|
20
|
-
registerResources(server, client, config.apiUrl);
|
|
21
|
-
registerPrompts(server);
|
|
22
|
-
|
|
23
|
-
// Log startup info (to stderr to not interfere with stdio protocol)
|
|
24
|
-
console.error(`ContextStream MCP server starting...`);
|
|
25
|
-
console.error(`API URL: ${config.apiUrl}`);
|
|
26
|
-
console.error(`Auth: ${config.apiKey ? 'API Key' : config.jwt ? 'JWT' : 'None'}`);
|
|
27
|
-
|
|
28
|
-
// Start stdio transport (works with Claude Code, Cursor, VS Code MCP config, Inspector)
|
|
29
|
-
const transport = new StdioServerTransport();
|
|
30
|
-
await server.connect(transport);
|
|
31
|
-
|
|
32
|
-
console.error('ContextStream MCP server connected and ready');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
main().catch((err) => {
|
|
36
|
-
console.error('ContextStream MCP server failed to start:', err?.message || err);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
});
|