midnight-mcp 0.0.2 → 0.0.5
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 +55 -54
- package/dist/pipeline/github.d.ts +18 -1
- package/dist/pipeline/github.js +132 -20
- package/dist/server.js +29 -7
- package/dist/tools/health.d.ts +91 -0
- package/dist/tools/health.js +91 -0
- package/dist/tools/index.d.ts +30 -4
- package/dist/tools/index.js +10 -3
- package/dist/tools/search.d.ts +5 -47
- package/dist/tools/search.js +167 -13
- package/dist/utils/cache.d.ts +77 -0
- package/dist/utils/cache.js +172 -0
- package/dist/utils/config.d.ts +16 -12
- package/dist/utils/config.js +25 -8
- package/dist/utils/errors.d.ts +45 -0
- package/dist/utils/errors.js +95 -0
- package/dist/utils/health.d.ts +29 -0
- package/dist/utils/health.js +132 -0
- package/dist/utils/hosted-api.d.ts +61 -0
- package/dist/utils/hosted-api.js +106 -0
- package/dist/utils/index.d.ts +12 -1
- package/dist/utils/index.js +12 -1
- package/dist/utils/logger.d.ts +30 -1
- package/dist/utils/logger.js +68 -3
- package/dist/utils/rate-limit.d.ts +61 -0
- package/dist/utils/rate-limit.js +148 -0
- package/dist/utils/validation.d.ts +52 -0
- package/dist/utils/validation.js +255 -0
- package/package.json +1 -2
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
declare const ConfigSchema: z.ZodObject<{
|
|
3
|
+
mode: z.ZodDefault<z.ZodEnum<["hosted", "local"]>>;
|
|
4
|
+
hostedApiUrl: z.ZodDefault<z.ZodString>;
|
|
3
5
|
githubToken: z.ZodOptional<z.ZodString>;
|
|
4
6
|
chromaUrl: z.ZodDefault<z.ZodString>;
|
|
5
|
-
qdrantUrl: z.ZodOptional<z.ZodString>;
|
|
6
|
-
pineconeApiKey: z.ZodOptional<z.ZodString>;
|
|
7
|
-
pineconeIndex: z.ZodOptional<z.ZodString>;
|
|
8
7
|
openaiApiKey: z.ZodOptional<z.ZodString>;
|
|
9
8
|
embeddingModel: z.ZodDefault<z.ZodString>;
|
|
10
9
|
logLevel: z.ZodDefault<z.ZodEnum<["debug", "info", "warn", "error"]>>;
|
|
@@ -13,6 +12,8 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
13
12
|
dataDir: z.ZodDefault<z.ZodString>;
|
|
14
13
|
cacheDir: z.ZodDefault<z.ZodString>;
|
|
15
14
|
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
mode: "hosted" | "local";
|
|
16
|
+
hostedApiUrl: string;
|
|
16
17
|
chromaUrl: string;
|
|
17
18
|
embeddingModel: string;
|
|
18
19
|
logLevel: "debug" | "info" | "warn" | "error";
|
|
@@ -21,16 +22,12 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
21
22
|
dataDir: string;
|
|
22
23
|
cacheDir: string;
|
|
23
24
|
githubToken?: string | undefined;
|
|
24
|
-
qdrantUrl?: string | undefined;
|
|
25
|
-
pineconeApiKey?: string | undefined;
|
|
26
|
-
pineconeIndex?: string | undefined;
|
|
27
25
|
openaiApiKey?: string | undefined;
|
|
28
26
|
}, {
|
|
27
|
+
mode?: "hosted" | "local" | undefined;
|
|
28
|
+
hostedApiUrl?: string | undefined;
|
|
29
29
|
githubToken?: string | undefined;
|
|
30
30
|
chromaUrl?: string | undefined;
|
|
31
|
-
qdrantUrl?: string | undefined;
|
|
32
|
-
pineconeApiKey?: string | undefined;
|
|
33
|
-
pineconeIndex?: string | undefined;
|
|
34
31
|
openaiApiKey?: string | undefined;
|
|
35
32
|
embeddingModel?: string | undefined;
|
|
36
33
|
logLevel?: "debug" | "info" | "warn" | "error" | undefined;
|
|
@@ -41,6 +38,8 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
41
38
|
}>;
|
|
42
39
|
export type Config = z.infer<typeof ConfigSchema>;
|
|
43
40
|
export declare const config: {
|
|
41
|
+
mode: "hosted" | "local";
|
|
42
|
+
hostedApiUrl: string;
|
|
44
43
|
chromaUrl: string;
|
|
45
44
|
embeddingModel: string;
|
|
46
45
|
logLevel: "debug" | "info" | "warn" | "error";
|
|
@@ -49,11 +48,16 @@ export declare const config: {
|
|
|
49
48
|
dataDir: string;
|
|
50
49
|
cacheDir: string;
|
|
51
50
|
githubToken?: string | undefined;
|
|
52
|
-
qdrantUrl?: string | undefined;
|
|
53
|
-
pineconeApiKey?: string | undefined;
|
|
54
|
-
pineconeIndex?: string | undefined;
|
|
55
51
|
openaiApiKey?: string | undefined;
|
|
56
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Check if running in hosted mode (default)
|
|
55
|
+
*/
|
|
56
|
+
export declare function isHostedMode(): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Check if running in local mode
|
|
59
|
+
*/
|
|
60
|
+
export declare function isLocalMode(): boolean;
|
|
57
61
|
export interface RepositoryConfig {
|
|
58
62
|
owner: string;
|
|
59
63
|
repo: string;
|
package/dist/utils/config.js
CHANGED
|
@@ -2,14 +2,17 @@ import { z } from "zod";
|
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
3
|
dotenv.config();
|
|
4
4
|
const ConfigSchema = z.object({
|
|
5
|
+
// Mode: 'hosted' (default) or 'local'
|
|
6
|
+
mode: z.enum(["hosted", "local"]).default("hosted"),
|
|
7
|
+
// Hosted API URL (used when mode is 'hosted')
|
|
8
|
+
hostedApiUrl: z
|
|
9
|
+
.string()
|
|
10
|
+
.default("https://midnight-mcp-api.midnightmcp.workers.dev"),
|
|
5
11
|
// GitHub
|
|
6
12
|
githubToken: z.string().optional(),
|
|
7
|
-
// Vector Database
|
|
13
|
+
// Vector Database (only needed for local mode)
|
|
8
14
|
chromaUrl: z.string().default("http://localhost:8000"),
|
|
9
|
-
|
|
10
|
-
pineconeApiKey: z.string().optional(),
|
|
11
|
-
pineconeIndex: z.string().optional(),
|
|
12
|
-
// Embeddings
|
|
15
|
+
// Embeddings (only needed for local mode)
|
|
13
16
|
openaiApiKey: z.string().optional(),
|
|
14
17
|
embeddingModel: z.string().default("text-embedding-3-small"),
|
|
15
18
|
// Server
|
|
@@ -21,12 +24,14 @@ const ConfigSchema = z.object({
|
|
|
21
24
|
cacheDir: z.string().default("./cache"),
|
|
22
25
|
});
|
|
23
26
|
function loadConfig() {
|
|
27
|
+
// Determine mode: local if MIDNIGHT_LOCAL=true or if OPENAI_API_KEY is set
|
|
28
|
+
const isLocalMode = process.env.MIDNIGHT_LOCAL === "true" ||
|
|
29
|
+
(process.env.OPENAI_API_KEY && process.env.CHROMA_URL);
|
|
24
30
|
const rawConfig = {
|
|
31
|
+
mode: isLocalMode ? "local" : "hosted",
|
|
32
|
+
hostedApiUrl: process.env.MIDNIGHT_API_URL,
|
|
25
33
|
githubToken: process.env.GITHUB_TOKEN,
|
|
26
34
|
chromaUrl: process.env.CHROMA_URL,
|
|
27
|
-
qdrantUrl: process.env.QDRANT_URL,
|
|
28
|
-
pineconeApiKey: process.env.PINECONE_API_KEY,
|
|
29
|
-
pineconeIndex: process.env.PINECONE_INDEX,
|
|
30
35
|
openaiApiKey: process.env.OPENAI_API_KEY,
|
|
31
36
|
embeddingModel: process.env.EMBEDDING_MODEL,
|
|
32
37
|
logLevel: process.env.LOG_LEVEL,
|
|
@@ -42,6 +47,18 @@ function loadConfig() {
|
|
|
42
47
|
return ConfigSchema.parse(cleanConfig);
|
|
43
48
|
}
|
|
44
49
|
export const config = loadConfig();
|
|
50
|
+
/**
|
|
51
|
+
* Check if running in hosted mode (default)
|
|
52
|
+
*/
|
|
53
|
+
export function isHostedMode() {
|
|
54
|
+
return config.mode === "hosted";
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if running in local mode
|
|
58
|
+
*/
|
|
59
|
+
export function isLocalMode() {
|
|
60
|
+
return config.mode === "local";
|
|
61
|
+
}
|
|
45
62
|
export const DEFAULT_REPOSITORIES = [
|
|
46
63
|
// Core Language & SDK
|
|
47
64
|
{
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-friendly error messages and error handling utilities
|
|
3
|
+
*/
|
|
4
|
+
export declare class MCPError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
readonly suggestion?: string;
|
|
7
|
+
readonly details?: Record<string, unknown>;
|
|
8
|
+
constructor(message: string, code: string, suggestion?: string, details?: Record<string, unknown>);
|
|
9
|
+
toJSON(): {
|
|
10
|
+
details?: Record<string, unknown> | undefined;
|
|
11
|
+
error: string;
|
|
12
|
+
code: string;
|
|
13
|
+
suggestion: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Common error codes
|
|
18
|
+
*/
|
|
19
|
+
export declare const ErrorCodes: {
|
|
20
|
+
readonly RATE_LIMIT: "RATE_LIMIT_EXCEEDED";
|
|
21
|
+
readonly NOT_FOUND: "RESOURCE_NOT_FOUND";
|
|
22
|
+
readonly NETWORK: "NETWORK_ERROR";
|
|
23
|
+
readonly INVALID_INPUT: "INVALID_INPUT";
|
|
24
|
+
readonly UNKNOWN_REPO: "UNKNOWN_REPOSITORY";
|
|
25
|
+
readonly PARSE_ERROR: "PARSE_ERROR";
|
|
26
|
+
readonly CHROMADB_UNAVAILABLE: "CHROMADB_UNAVAILABLE";
|
|
27
|
+
readonly OPENAI_UNAVAILABLE: "OPENAI_UNAVAILABLE";
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Create user-friendly error from various error types
|
|
31
|
+
*/
|
|
32
|
+
export declare function createUserError(error: unknown, context?: string): MCPError;
|
|
33
|
+
/**
|
|
34
|
+
* Format error for MCP response
|
|
35
|
+
*/
|
|
36
|
+
export declare function formatErrorResponse(error: unknown, context?: string): {
|
|
37
|
+
error: string;
|
|
38
|
+
code: string;
|
|
39
|
+
suggestion?: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Wrap a function with error handling
|
|
43
|
+
*/
|
|
44
|
+
export declare function withErrorHandling<T extends (...args: unknown[]) => Promise<unknown>>(fn: T, context: string): T;
|
|
45
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-friendly error messages and error handling utilities
|
|
3
|
+
*/
|
|
4
|
+
export class MCPError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
suggestion;
|
|
7
|
+
details;
|
|
8
|
+
constructor(message, code, suggestion, details) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "MCPError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.suggestion = suggestion;
|
|
13
|
+
this.details = details;
|
|
14
|
+
}
|
|
15
|
+
toJSON() {
|
|
16
|
+
return {
|
|
17
|
+
error: this.message,
|
|
18
|
+
code: this.code,
|
|
19
|
+
suggestion: this.suggestion,
|
|
20
|
+
...(this.details && { details: this.details }),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Common error codes
|
|
26
|
+
*/
|
|
27
|
+
export const ErrorCodes = {
|
|
28
|
+
RATE_LIMIT: "RATE_LIMIT_EXCEEDED",
|
|
29
|
+
NOT_FOUND: "RESOURCE_NOT_FOUND",
|
|
30
|
+
NETWORK: "NETWORK_ERROR",
|
|
31
|
+
INVALID_INPUT: "INVALID_INPUT",
|
|
32
|
+
UNKNOWN_REPO: "UNKNOWN_REPOSITORY",
|
|
33
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
34
|
+
CHROMADB_UNAVAILABLE: "CHROMADB_UNAVAILABLE",
|
|
35
|
+
OPENAI_UNAVAILABLE: "OPENAI_UNAVAILABLE",
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Create user-friendly error from various error types
|
|
39
|
+
*/
|
|
40
|
+
export function createUserError(error, context) {
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
const ctx = context ? ` while ${context}` : "";
|
|
43
|
+
// Rate limit errors
|
|
44
|
+
if (message.includes("rate limit") ||
|
|
45
|
+
message.includes("403") ||
|
|
46
|
+
message.includes("API rate limit")) {
|
|
47
|
+
return new MCPError(`GitHub API rate limit exceeded${ctx}`, ErrorCodes.RATE_LIMIT, "Add GITHUB_TOKEN to your config to increase limits from 60 to 5000 requests/hour. " +
|
|
48
|
+
"Get a token at https://github.com/settings/tokens");
|
|
49
|
+
}
|
|
50
|
+
// Not found errors
|
|
51
|
+
if (message.includes("404") || message.includes("Not Found")) {
|
|
52
|
+
return new MCPError(`Resource not found${ctx}`, ErrorCodes.NOT_FOUND, "Check that the repository, file, or version exists and is publicly accessible.");
|
|
53
|
+
}
|
|
54
|
+
// Network errors
|
|
55
|
+
if (message.includes("network") ||
|
|
56
|
+
message.includes("ECONNREFUSED") ||
|
|
57
|
+
message.includes("ETIMEDOUT") ||
|
|
58
|
+
message.includes("timeout")) {
|
|
59
|
+
return new MCPError(`Network error${ctx}`, ErrorCodes.NETWORK, "Check your internet connection and try again. If the problem persists, " +
|
|
60
|
+
"the service may be temporarily unavailable.");
|
|
61
|
+
}
|
|
62
|
+
// ChromaDB errors
|
|
63
|
+
if (message.includes("chroma") || message.includes("8000")) {
|
|
64
|
+
return new MCPError(`ChromaDB is not available${ctx}`, ErrorCodes.CHROMADB_UNAVAILABLE, "ChromaDB is optional. Without it, search uses keyword matching instead of semantic search. " +
|
|
65
|
+
"To enable semantic search, run: docker run -d -p 8000:8000 chromadb/chroma");
|
|
66
|
+
}
|
|
67
|
+
// OpenAI errors
|
|
68
|
+
if (message.includes("openai") || message.includes("embedding")) {
|
|
69
|
+
return new MCPError(`OpenAI API error${ctx}`, ErrorCodes.OPENAI_UNAVAILABLE, "OpenAI is optional. Without it, search uses keyword matching. " +
|
|
70
|
+
"To enable semantic search, add OPENAI_API_KEY to your config.");
|
|
71
|
+
}
|
|
72
|
+
// Default error
|
|
73
|
+
return new MCPError(`An error occurred${ctx}: ${message}`, "UNKNOWN_ERROR", "If this problem persists, please report it at https://github.com/Olanetsoft/midnight-mcp/issues");
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Format error for MCP response
|
|
77
|
+
*/
|
|
78
|
+
export function formatErrorResponse(error, context) {
|
|
79
|
+
const mcpError = error instanceof MCPError ? error : createUserError(error, context);
|
|
80
|
+
return mcpError.toJSON();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Wrap a function with error handling
|
|
84
|
+
*/
|
|
85
|
+
export function withErrorHandling(fn, context) {
|
|
86
|
+
return (async (...args) => {
|
|
87
|
+
try {
|
|
88
|
+
return await fn(...args);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw createUserError(error, context);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health check utilities for MCP server monitoring
|
|
3
|
+
*/
|
|
4
|
+
export interface HealthStatus {
|
|
5
|
+
status: "healthy" | "degraded" | "unhealthy";
|
|
6
|
+
timestamp: string;
|
|
7
|
+
version: string;
|
|
8
|
+
uptime: number;
|
|
9
|
+
checks: {
|
|
10
|
+
name: string;
|
|
11
|
+
status: "pass" | "warn" | "fail";
|
|
12
|
+
message?: string;
|
|
13
|
+
latency?: number;
|
|
14
|
+
}[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Perform a full health check
|
|
18
|
+
*/
|
|
19
|
+
export declare function getHealthStatus(): Promise<HealthStatus>;
|
|
20
|
+
/**
|
|
21
|
+
* Get a quick health check (no external calls)
|
|
22
|
+
*/
|
|
23
|
+
export declare function getQuickHealthStatus(): Omit<HealthStatus, "checks"> & {
|
|
24
|
+
checks: {
|
|
25
|
+
name: string;
|
|
26
|
+
status: "pass";
|
|
27
|
+
}[];
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health check utilities for MCP server monitoring
|
|
3
|
+
*/
|
|
4
|
+
import { githubClient } from "../pipeline/index.js";
|
|
5
|
+
// Track server start time
|
|
6
|
+
const startTime = Date.now();
|
|
7
|
+
// Get package version
|
|
8
|
+
const VERSION = process.env.npm_package_version || "0.0.3";
|
|
9
|
+
/**
|
|
10
|
+
* Check if GitHub API is accessible
|
|
11
|
+
*/
|
|
12
|
+
async function checkGitHubAPI() {
|
|
13
|
+
const start = Date.now();
|
|
14
|
+
try {
|
|
15
|
+
// Try to get rate limit info (lightweight API call)
|
|
16
|
+
const rateLimit = await githubClient.getRateLimit();
|
|
17
|
+
const latency = Date.now() - start;
|
|
18
|
+
if (rateLimit.remaining < 100) {
|
|
19
|
+
return {
|
|
20
|
+
status: "warn",
|
|
21
|
+
message: `Rate limit low: ${rateLimit.remaining}/${rateLimit.limit} remaining`,
|
|
22
|
+
latency,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
status: "pass",
|
|
27
|
+
message: `Rate limit: ${rateLimit.remaining}/${rateLimit.limit}`,
|
|
28
|
+
latency,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
status: "fail",
|
|
34
|
+
message: `GitHub API error: ${error instanceof Error ? error.message : String(error)}`,
|
|
35
|
+
latency: Date.now() - start,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if ChromaDB is accessible (optional dependency)
|
|
41
|
+
*/
|
|
42
|
+
async function checkVectorStore() {
|
|
43
|
+
try {
|
|
44
|
+
// Import dynamically to handle optional dependency
|
|
45
|
+
const { vectorStore } = await import("../db/index.js");
|
|
46
|
+
// Check if vector store is initialized
|
|
47
|
+
if (vectorStore) {
|
|
48
|
+
return {
|
|
49
|
+
status: "pass",
|
|
50
|
+
message: "Vector store available",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
status: "warn",
|
|
55
|
+
message: "Vector store not initialized (semantic search unavailable)",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return {
|
|
60
|
+
status: "warn",
|
|
61
|
+
message: "Vector store not configured (semantic search unavailable)",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check memory usage
|
|
67
|
+
*/
|
|
68
|
+
function checkMemory() {
|
|
69
|
+
const usage = process.memoryUsage();
|
|
70
|
+
const heapUsedMB = Math.round(usage.heapUsed / 1024 / 1024);
|
|
71
|
+
const heapTotalMB = Math.round(usage.heapTotal / 1024 / 1024);
|
|
72
|
+
const percentUsed = Math.round((usage.heapUsed / usage.heapTotal) * 100);
|
|
73
|
+
if (percentUsed > 90) {
|
|
74
|
+
return {
|
|
75
|
+
status: "fail",
|
|
76
|
+
message: `High memory usage: ${heapUsedMB}MB/${heapTotalMB}MB (${percentUsed}%)`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (percentUsed > 75) {
|
|
80
|
+
return {
|
|
81
|
+
status: "warn",
|
|
82
|
+
message: `Elevated memory usage: ${heapUsedMB}MB/${heapTotalMB}MB (${percentUsed}%)`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
status: "pass",
|
|
87
|
+
message: `Memory: ${heapUsedMB}MB/${heapTotalMB}MB (${percentUsed}%)`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Perform a full health check
|
|
92
|
+
*/
|
|
93
|
+
export async function getHealthStatus() {
|
|
94
|
+
const checks = [];
|
|
95
|
+
// Run all health checks in parallel
|
|
96
|
+
const [githubCheck, vectorCheck] = await Promise.all([
|
|
97
|
+
checkGitHubAPI(),
|
|
98
|
+
checkVectorStore(),
|
|
99
|
+
]);
|
|
100
|
+
const memoryCheck = checkMemory();
|
|
101
|
+
checks.push({ name: "github_api", ...githubCheck }, { name: "vector_store", ...vectorCheck }, { name: "memory", ...memoryCheck });
|
|
102
|
+
// Determine overall status
|
|
103
|
+
const hasFailure = checks.some((c) => c.status === "fail");
|
|
104
|
+
const hasWarning = checks.some((c) => c.status === "warn");
|
|
105
|
+
let status = "healthy";
|
|
106
|
+
if (hasFailure) {
|
|
107
|
+
status = "unhealthy";
|
|
108
|
+
}
|
|
109
|
+
else if (hasWarning) {
|
|
110
|
+
status = "degraded";
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
status,
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
version: VERSION,
|
|
116
|
+
uptime: Math.round((Date.now() - startTime) / 1000),
|
|
117
|
+
checks,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get a quick health check (no external calls)
|
|
122
|
+
*/
|
|
123
|
+
export function getQuickHealthStatus() {
|
|
124
|
+
return {
|
|
125
|
+
status: "healthy",
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
version: VERSION,
|
|
128
|
+
uptime: Math.round((Date.now() - startTime) / 1000),
|
|
129
|
+
checks: [{ name: "server", status: "pass" }],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the hosted Midnight MCP API
|
|
3
|
+
* Used when running in hosted mode (default)
|
|
4
|
+
*/
|
|
5
|
+
export interface HostedSearchResult {
|
|
6
|
+
code?: string;
|
|
7
|
+
content?: string;
|
|
8
|
+
relevanceScore: number;
|
|
9
|
+
source: {
|
|
10
|
+
repository: string;
|
|
11
|
+
filePath: string;
|
|
12
|
+
lines?: string;
|
|
13
|
+
section?: string;
|
|
14
|
+
};
|
|
15
|
+
codeType?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
isExported?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface HostedSearchResponse {
|
|
20
|
+
results: HostedSearchResult[];
|
|
21
|
+
totalResults: number;
|
|
22
|
+
query: string;
|
|
23
|
+
category?: string;
|
|
24
|
+
warnings?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface HostedSearchFilter {
|
|
27
|
+
language?: string;
|
|
28
|
+
repository?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Search Compact code via hosted API
|
|
32
|
+
*/
|
|
33
|
+
export declare function searchCompactHosted(query: string, limit?: number): Promise<HostedSearchResponse>;
|
|
34
|
+
/**
|
|
35
|
+
* Search TypeScript code via hosted API
|
|
36
|
+
*/
|
|
37
|
+
export declare function searchTypeScriptHosted(query: string, limit?: number, includeTypes?: boolean): Promise<HostedSearchResponse>;
|
|
38
|
+
/**
|
|
39
|
+
* Search documentation via hosted API
|
|
40
|
+
*/
|
|
41
|
+
export declare function searchDocsHosted(query: string, limit?: number, category?: string): Promise<HostedSearchResponse>;
|
|
42
|
+
/**
|
|
43
|
+
* Generic search via hosted API
|
|
44
|
+
*/
|
|
45
|
+
export declare function searchHosted(query: string, limit?: number, filter?: HostedSearchFilter): Promise<HostedSearchResponse>;
|
|
46
|
+
/**
|
|
47
|
+
* Check if the hosted API is available
|
|
48
|
+
*/
|
|
49
|
+
export declare function checkHostedApiHealth(): Promise<{
|
|
50
|
+
available: boolean;
|
|
51
|
+
documentsIndexed?: number;
|
|
52
|
+
error?: string;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Get hosted API stats
|
|
56
|
+
*/
|
|
57
|
+
export declare function getHostedApiStats(): Promise<{
|
|
58
|
+
documentsIndexed: number;
|
|
59
|
+
repositories: number;
|
|
60
|
+
}>;
|
|
61
|
+
//# sourceMappingURL=hosted-api.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the hosted Midnight MCP API
|
|
3
|
+
* Used when running in hosted mode (default)
|
|
4
|
+
*/
|
|
5
|
+
import { config, logger } from "./index.js";
|
|
6
|
+
const API_TIMEOUT = 10000; // 10 seconds
|
|
7
|
+
/**
|
|
8
|
+
* Make a request to the hosted API
|
|
9
|
+
*/
|
|
10
|
+
async function apiRequest(endpoint, options = {}) {
|
|
11
|
+
const url = `${config.hostedApiUrl}${endpoint}`;
|
|
12
|
+
const controller = new AbortController();
|
|
13
|
+
const timeout = setTimeout(() => controller.abort(), API_TIMEOUT);
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch(url, {
|
|
16
|
+
...options,
|
|
17
|
+
signal: controller.signal,
|
|
18
|
+
headers: {
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
"User-Agent": "midnight-mcp",
|
|
21
|
+
...options.headers,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const errorData = (await response
|
|
26
|
+
.json()
|
|
27
|
+
.catch(() => ({ error: "Unknown error" })));
|
|
28
|
+
throw new Error(errorData.error || `API error: ${response.status}`);
|
|
29
|
+
}
|
|
30
|
+
return (await response.json());
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
34
|
+
throw new Error("API request timed out. The hosted service may be unavailable.");
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
clearTimeout(timeout);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Search Compact code via hosted API
|
|
44
|
+
*/
|
|
45
|
+
export async function searchCompactHosted(query, limit = 10) {
|
|
46
|
+
logger.debug("Searching Compact code via hosted API", { query });
|
|
47
|
+
return apiRequest("/v1/search/compact", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
body: JSON.stringify({ query, limit }),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Search TypeScript code via hosted API
|
|
54
|
+
*/
|
|
55
|
+
export async function searchTypeScriptHosted(query, limit = 10, includeTypes = true) {
|
|
56
|
+
logger.debug("Searching TypeScript code via hosted API", { query });
|
|
57
|
+
return apiRequest("/v1/search/typescript", {
|
|
58
|
+
method: "POST",
|
|
59
|
+
body: JSON.stringify({ query, limit, includeTypes }),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Search documentation via hosted API
|
|
64
|
+
*/
|
|
65
|
+
export async function searchDocsHosted(query, limit = 10, category = "all") {
|
|
66
|
+
logger.debug("Searching documentation via hosted API", { query });
|
|
67
|
+
return apiRequest("/v1/search/docs", {
|
|
68
|
+
method: "POST",
|
|
69
|
+
body: JSON.stringify({ query, limit, category }),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Generic search via hosted API
|
|
74
|
+
*/
|
|
75
|
+
export async function searchHosted(query, limit = 10, filter) {
|
|
76
|
+
logger.debug("Searching via hosted API", { query, filter });
|
|
77
|
+
return apiRequest("/v1/search", {
|
|
78
|
+
method: "POST",
|
|
79
|
+
body: JSON.stringify({ query, limit, filter }),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if the hosted API is available
|
|
84
|
+
*/
|
|
85
|
+
export async function checkHostedApiHealth() {
|
|
86
|
+
try {
|
|
87
|
+
const response = await apiRequest("/health");
|
|
88
|
+
return {
|
|
89
|
+
available: response.status === "healthy",
|
|
90
|
+
documentsIndexed: response.vectorStore?.documentsIndexed,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
available: false,
|
|
96
|
+
error: error instanceof Error ? error.message : String(error),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get hosted API stats
|
|
102
|
+
*/
|
|
103
|
+
export async function getHostedApiStats() {
|
|
104
|
+
return apiRequest("/v1/stats");
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=hosted-api.js.map
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
export { config } from "./config.js";
|
|
1
|
+
export { config, isHostedMode, isLocalMode } from "./config.js";
|
|
2
2
|
export type { Config, RepositoryConfig } from "./config.js";
|
|
3
3
|
export { DEFAULT_REPOSITORIES } from "./config.js";
|
|
4
4
|
export { logger } from "./logger.js";
|
|
5
|
+
export { MCPError, ErrorCodes, createUserError, formatErrorResponse, withErrorHandling, } from "./errors.js";
|
|
6
|
+
export { validateQuery, validateRepository, validatePath, validateRef, validateNumber, validateToolArgs, sanitizeString, } from "./validation.js";
|
|
7
|
+
export type { ValidationResult } from "./validation.js";
|
|
8
|
+
export { getHealthStatus, getQuickHealthStatus } from "./health.js";
|
|
9
|
+
export type { HealthStatus } from "./health.js";
|
|
10
|
+
export { updateRateLimitFromHeaders, updateRateLimit, getRateLimitStatus, shouldProceedWithRequest, getTimeUntilReset, formatRateLimitStatus, decrementRemaining, } from "./rate-limit.js";
|
|
11
|
+
export type { RateLimitInfo, RateLimitStatus } from "./rate-limit.js";
|
|
12
|
+
export { Cache, createCacheKey, searchCache, fileCache, metadataCache, pruneAllCaches, } from "./cache.js";
|
|
13
|
+
export type { CacheOptions, CacheEntry, CacheStats } from "./cache.js";
|
|
14
|
+
export { searchCompactHosted, searchTypeScriptHosted, searchDocsHosted, searchHosted, checkHostedApiHealth, getHostedApiStats, } from "./hosted-api.js";
|
|
15
|
+
export type { HostedSearchResult, HostedSearchResponse, HostedSearchFilter, } from "./hosted-api.js";
|
|
5
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/utils/index.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
export { config } from "./config.js";
|
|
1
|
+
export { config, isHostedMode, isLocalMode } from "./config.js";
|
|
2
2
|
export { DEFAULT_REPOSITORIES } from "./config.js";
|
|
3
3
|
export { logger } from "./logger.js";
|
|
4
|
+
export { MCPError, ErrorCodes, createUserError, formatErrorResponse, withErrorHandling, } from "./errors.js";
|
|
5
|
+
// Validation utilities
|
|
6
|
+
export { validateQuery, validateRepository, validatePath, validateRef, validateNumber, validateToolArgs, sanitizeString, } from "./validation.js";
|
|
7
|
+
// Health check utilities
|
|
8
|
+
export { getHealthStatus, getQuickHealthStatus } from "./health.js";
|
|
9
|
+
// Rate limit tracking
|
|
10
|
+
export { updateRateLimitFromHeaders, updateRateLimit, getRateLimitStatus, shouldProceedWithRequest, getTimeUntilReset, formatRateLimitStatus, decrementRemaining, } from "./rate-limit.js";
|
|
11
|
+
// Caching utilities
|
|
12
|
+
export { Cache, createCacheKey, searchCache, fileCache, metadataCache, pruneAllCaches, } from "./cache.js";
|
|
13
|
+
// Hosted API client
|
|
14
|
+
export { searchCompactHosted, searchTypeScriptHosted, searchDocsHosted, searchHosted, checkHostedApiHealth, getHostedApiStats, } from "./hosted-api.js";
|
|
4
15
|
//# sourceMappingURL=index.js.map
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -1,13 +1,42 @@
|
|
|
1
1
|
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
type LogFormat = "text" | "json";
|
|
2
3
|
declare class Logger {
|
|
3
4
|
private level;
|
|
4
|
-
|
|
5
|
+
private format;
|
|
6
|
+
private service;
|
|
7
|
+
constructor(level?: LogLevel, format?: LogFormat, service?: string);
|
|
8
|
+
/**
|
|
9
|
+
* Set log format at runtime
|
|
10
|
+
*/
|
|
11
|
+
setFormat(format: LogFormat): void;
|
|
12
|
+
/**
|
|
13
|
+
* Set log level at runtime
|
|
14
|
+
*/
|
|
15
|
+
setLevel(level: LogLevel): void;
|
|
5
16
|
private shouldLog;
|
|
17
|
+
private formatTextMessage;
|
|
18
|
+
private formatJsonMessage;
|
|
6
19
|
private formatMessage;
|
|
7
20
|
debug(message: string, meta?: object): void;
|
|
8
21
|
info(message: string, meta?: object): void;
|
|
9
22
|
warn(message: string, meta?: object): void;
|
|
10
23
|
error(message: string, meta?: object): void;
|
|
24
|
+
/**
|
|
25
|
+
* Create a child logger with additional context
|
|
26
|
+
*/
|
|
27
|
+
child(context: object): ChildLogger;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Child logger that includes additional context in all log messages
|
|
31
|
+
*/
|
|
32
|
+
declare class ChildLogger {
|
|
33
|
+
private parent;
|
|
34
|
+
private context;
|
|
35
|
+
constructor(parent: Logger, context: object);
|
|
36
|
+
debug(message: string, meta?: object): void;
|
|
37
|
+
info(message: string, meta?: object): void;
|
|
38
|
+
warn(message: string, meta?: object): void;
|
|
39
|
+
error(message: string, meta?: object): void;
|
|
11
40
|
}
|
|
12
41
|
export declare const logger: Logger;
|
|
13
42
|
export {};
|