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/README.md
CHANGED
|
@@ -17,29 +17,21 @@ Add to your `claude_desktop_config.json`:
|
|
|
17
17
|
}
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Restart Claude Desktop.
|
|
21
|
-
|
|
22
|
-
> **Note:** Search features won't work well without the full setup below.
|
|
20
|
+
Restart Claude Desktop. All features work out of the box—no API keys or setup required.
|
|
23
21
|
|
|
24
22
|
---
|
|
25
23
|
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
To enable semantic search across Midnight contracts and docs:
|
|
29
|
-
|
|
30
|
-
### 1. Start ChromaDB
|
|
31
|
-
|
|
32
|
-
ChromaDB is a local vector database—no account needed, just Docker:
|
|
24
|
+
## How It Works
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
docker run -d -p 8000:8000 chromadb/chroma
|
|
36
|
-
```
|
|
26
|
+
By default, the MCP uses a **hosted API** for semantic search:
|
|
37
27
|
|
|
38
|
-
|
|
28
|
+
- ✅ **Zero configuration** — just install and use
|
|
29
|
+
- ✅ **Semantic search** works immediately
|
|
30
|
+
- ✅ **No API keys** needed
|
|
39
31
|
|
|
40
|
-
|
|
32
|
+
### Local Mode (Optional)
|
|
41
33
|
|
|
42
|
-
|
|
34
|
+
Run everything locally for privacy or offline use:
|
|
43
35
|
|
|
44
36
|
```json
|
|
45
37
|
{
|
|
@@ -48,6 +40,7 @@ Needed for generating embeddings. Get one at [platform.openai.com/api-keys](http
|
|
|
48
40
|
"command": "npx",
|
|
49
41
|
"args": ["-y", "midnight-mcp"],
|
|
50
42
|
"env": {
|
|
43
|
+
"MIDNIGHT_LOCAL": "true",
|
|
51
44
|
"OPENAI_API_KEY": "sk-...",
|
|
52
45
|
"CHROMA_URL": "http://localhost:8000"
|
|
53
46
|
}
|
|
@@ -56,28 +49,38 @@ Needed for generating embeddings. Get one at [platform.openai.com/api-keys](http
|
|
|
56
49
|
}
|
|
57
50
|
```
|
|
58
51
|
|
|
59
|
-
|
|
52
|
+
Local mode requires ChromaDB (`docker run -d -p 8000:8000 chromadb/chroma`) and an OpenAI API key.
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
---
|
|
54
|
+
### GitHub Token (Optional)
|
|
64
55
|
|
|
65
|
-
|
|
56
|
+
Add `"GITHUB_TOKEN": "ghp_..."` for higher GitHub API rate limits (60 → 5000 requests/hour).
|
|
66
57
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
| Tool | Description |
|
|
70
|
-
| ----------------------------- | --------------------------------------- |
|
|
71
|
-
| `midnight:search-compact` | Search Compact contract code |
|
|
72
|
-
| `midnight:search-typescript` | Search TypeScript SDK |
|
|
73
|
-
| `midnight:search-docs` | Search documentation |
|
|
74
|
-
| `midnight:analyze-contract` | Analyze contract structure and security |
|
|
75
|
-
| `midnight:explain-circuit` | Explain circuits in plain language |
|
|
76
|
-
| `midnight:get-file` | Get files from Midnight repos |
|
|
77
|
-
| `midnight:list-examples` | List example contracts |
|
|
78
|
-
| `midnight:get-latest-updates` | Recent repo changes |
|
|
58
|
+
---
|
|
79
59
|
|
|
80
|
-
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
### Tools (16)
|
|
63
|
+
|
|
64
|
+
| Tool | Description |
|
|
65
|
+
| --------------------------------- | --------------------------------------- |
|
|
66
|
+
| `midnight-search-compact` | Search Compact contract code |
|
|
67
|
+
| `midnight-search-typescript` | Search TypeScript SDK |
|
|
68
|
+
| `midnight-search-docs` | Search documentation |
|
|
69
|
+
| `midnight-analyze-contract` | Analyze contract structure and security |
|
|
70
|
+
| `midnight-explain-circuit` | Explain circuits in plain language |
|
|
71
|
+
| `midnight-get-file` | Get files from Midnight repos |
|
|
72
|
+
| `midnight-list-examples` | List example contracts |
|
|
73
|
+
| `midnight-get-latest-updates` | Recent repo changes |
|
|
74
|
+
| `midnight-get-version-info` | Get version and release info |
|
|
75
|
+
| `midnight-check-breaking-changes` | Check for breaking changes |
|
|
76
|
+
| `midnight-get-migration-guide` | Migration guides between versions |
|
|
77
|
+
| `midnight-get-file-at-version` | Get file at specific version |
|
|
78
|
+
| `midnight-compare-syntax` | Compare files between versions |
|
|
79
|
+
| `midnight-get-latest-syntax` | Latest syntax reference |
|
|
80
|
+
| `midnight-health-check` | Check server health status |
|
|
81
|
+
| `midnight-get-status` | Get rate limits and cache stats |
|
|
82
|
+
|
|
83
|
+
### Resources (20)
|
|
81
84
|
|
|
82
85
|
- `midnight://docs/*` — Documentation (Compact reference, SDK API, ZK concepts)
|
|
83
86
|
- `midnight://code/*` — Examples, patterns, and templates
|
|
@@ -85,16 +88,17 @@ Add `"GITHUB_TOKEN": "ghp_..."` to increase API rate limits from 60 to 5000 requ
|
|
|
85
88
|
|
|
86
89
|
### Prompts
|
|
87
90
|
|
|
88
|
-
- `midnight
|
|
89
|
-
- `midnight
|
|
90
|
-
- `midnight
|
|
91
|
-
- `midnight
|
|
91
|
+
- `midnight-create-contract` — Create new contracts
|
|
92
|
+
- `midnight-review-contract` — Security review
|
|
93
|
+
- `midnight-explain-concept` — Learn Midnight concepts
|
|
94
|
+
- `midnight-compare-approaches` — Compare implementation approaches
|
|
95
|
+
- `midnight-debug-contract` — Debug issues
|
|
92
96
|
|
|
93
97
|
---
|
|
94
98
|
|
|
95
99
|
## Developer Setup
|
|
96
100
|
|
|
97
|
-
For contributors
|
|
101
|
+
For contributors:
|
|
98
102
|
|
|
99
103
|
```bash
|
|
100
104
|
git clone https://github.com/Olanetsoft/midnight-mcp.git
|
|
@@ -104,26 +108,23 @@ npm run build
|
|
|
104
108
|
npm test
|
|
105
109
|
```
|
|
106
110
|
|
|
107
|
-
###
|
|
111
|
+
### Testing with Local API
|
|
112
|
+
|
|
113
|
+
To test against a local API server instead of production:
|
|
108
114
|
|
|
109
115
|
```bash
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
# Terminal 1: Start local API
|
|
117
|
+
cd api
|
|
118
|
+
npm install
|
|
119
|
+
npm run dev # Starts at http://localhost:8787
|
|
120
|
+
|
|
121
|
+
# Terminal 2: Run MCP with local API
|
|
122
|
+
MIDNIGHT_API_URL=http://localhost:8787 npm start
|
|
112
123
|
```
|
|
113
124
|
|
|
114
|
-
###
|
|
125
|
+
### API Backend
|
|
115
126
|
|
|
116
|
-
|
|
117
|
-
src/
|
|
118
|
-
├── index.ts # Entry point
|
|
119
|
-
├── server.ts # MCP server handlers
|
|
120
|
-
├── tools/ # Search, analysis, repository tools
|
|
121
|
-
├── resources/ # Docs, code, schema providers
|
|
122
|
-
├── prompts/ # Prompt templates
|
|
123
|
-
├── pipeline/ # GitHub sync & parsing
|
|
124
|
-
├── db/ # ChromaDB integration
|
|
125
|
-
└── utils/ # Config & logging
|
|
126
|
-
```
|
|
127
|
+
The hosted API runs on Cloudflare Workers + Vectorize. See [api/README.md](./api/README.md) for deployment and development instructions.
|
|
127
128
|
|
|
128
129
|
## License
|
|
129
130
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RepositoryConfig } from "../utils/index.js";
|
|
1
|
+
import { RepositoryConfig, getRateLimitStatus } from "../utils/index.js";
|
|
2
2
|
export interface GitHubFile {
|
|
3
3
|
path: string;
|
|
4
4
|
content: string;
|
|
@@ -62,6 +62,23 @@ export declare class GitHubClient {
|
|
|
62
62
|
repository: string;
|
|
63
63
|
url: string;
|
|
64
64
|
}>>;
|
|
65
|
+
/**
|
|
66
|
+
* Get current rate limit status from GitHub API
|
|
67
|
+
*/
|
|
68
|
+
getRateLimit(): Promise<{
|
|
69
|
+
limit: number;
|
|
70
|
+
remaining: number;
|
|
71
|
+
reset: Date;
|
|
72
|
+
used: number;
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Check if it's safe to make API requests
|
|
76
|
+
*/
|
|
77
|
+
checkRateLimit(): {
|
|
78
|
+
proceed: boolean;
|
|
79
|
+
reason?: string;
|
|
80
|
+
status: ReturnType<typeof getRateLimitStatus>;
|
|
81
|
+
};
|
|
65
82
|
}
|
|
66
83
|
export declare const githubClient: GitHubClient;
|
|
67
84
|
//# sourceMappingURL=github.d.ts.map
|
package/dist/pipeline/github.js
CHANGED
|
@@ -1,5 +1,87 @@
|
|
|
1
1
|
import { Octokit } from "octokit";
|
|
2
|
-
import { config, logger } from "../utils/index.js";
|
|
2
|
+
import { config, logger, updateRateLimit, shouldProceedWithRequest, getRateLimitStatus, } from "../utils/index.js";
|
|
3
|
+
// Retry configuration
|
|
4
|
+
const RETRY_CONFIG = {
|
|
5
|
+
maxRetries: 3,
|
|
6
|
+
baseDelayMs: 1000,
|
|
7
|
+
maxDelayMs: 10000,
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Retry wrapper with exponential backoff
|
|
11
|
+
*/
|
|
12
|
+
async function withRetry(operation, operationName) {
|
|
13
|
+
let lastError = null;
|
|
14
|
+
for (let attempt = 1; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
15
|
+
try {
|
|
16
|
+
return await operation();
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
lastError = error;
|
|
20
|
+
const isRetryable = isRetryableError(error);
|
|
21
|
+
if (!isRetryable || attempt === RETRY_CONFIG.maxRetries) {
|
|
22
|
+
logger.error(`${operationName} failed after ${attempt} attempt(s)`, {
|
|
23
|
+
error: String(error),
|
|
24
|
+
attempt,
|
|
25
|
+
});
|
|
26
|
+
throw enhanceError(error, operationName);
|
|
27
|
+
}
|
|
28
|
+
const delay = Math.min(RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt - 1), RETRY_CONFIG.maxDelayMs);
|
|
29
|
+
logger.warn(`${operationName} failed, retrying in ${delay}ms...`, {
|
|
30
|
+
attempt,
|
|
31
|
+
error: String(error),
|
|
32
|
+
});
|
|
33
|
+
await sleep(delay);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
throw lastError;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if an error is retryable
|
|
40
|
+
*/
|
|
41
|
+
function isRetryableError(error) {
|
|
42
|
+
if (error instanceof Error) {
|
|
43
|
+
const message = error.message.toLowerCase();
|
|
44
|
+
// Retry on network errors, rate limits, and server errors
|
|
45
|
+
return (message.includes("network") ||
|
|
46
|
+
message.includes("timeout") ||
|
|
47
|
+
message.includes("econnreset") ||
|
|
48
|
+
message.includes("rate limit") ||
|
|
49
|
+
message.includes("403") ||
|
|
50
|
+
message.includes("500") ||
|
|
51
|
+
message.includes("502") ||
|
|
52
|
+
message.includes("503") ||
|
|
53
|
+
message.includes("504"));
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Enhance error with more context
|
|
59
|
+
*/
|
|
60
|
+
function enhanceError(error, operation) {
|
|
61
|
+
const originalMessage = error instanceof Error ? error.message : String(error);
|
|
62
|
+
// Provide user-friendly error messages
|
|
63
|
+
if (originalMessage.includes("rate limit") ||
|
|
64
|
+
originalMessage.includes("403")) {
|
|
65
|
+
return new Error(`GitHub API rate limit exceeded during ${operation}. ` +
|
|
66
|
+
`Add a GITHUB_TOKEN to your config to increase limits from 60 to 5000 requests/hour.`);
|
|
67
|
+
}
|
|
68
|
+
if (originalMessage.includes("404")) {
|
|
69
|
+
return new Error(`Resource not found during ${operation}. ` +
|
|
70
|
+
`Check that the repository/file exists and is accessible.`);
|
|
71
|
+
}
|
|
72
|
+
if (originalMessage.includes("timeout") ||
|
|
73
|
+
originalMessage.includes("network")) {
|
|
74
|
+
return new Error(`Network error during ${operation}. ` +
|
|
75
|
+
`Check your internet connection and try again.`);
|
|
76
|
+
}
|
|
77
|
+
return new Error(`${operation} failed: ${originalMessage}`);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Sleep for a given number of milliseconds
|
|
81
|
+
*/
|
|
82
|
+
function sleep(ms) {
|
|
83
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
84
|
+
}
|
|
3
85
|
class SimpleCache {
|
|
4
86
|
cache = new Map();
|
|
5
87
|
ttlMs;
|
|
@@ -47,15 +129,8 @@ export class GitHubClient {
|
|
|
47
129
|
return cached;
|
|
48
130
|
}
|
|
49
131
|
try {
|
|
50
|
-
const { data: repoData } = await this.octokit.rest.repos.get({
|
|
51
|
-
|
|
52
|
-
repo,
|
|
53
|
-
});
|
|
54
|
-
const { data: commits } = await this.octokit.rest.repos.listCommits({
|
|
55
|
-
owner,
|
|
56
|
-
repo,
|
|
57
|
-
per_page: 1,
|
|
58
|
-
});
|
|
132
|
+
const { data: repoData } = await withRetry(() => this.octokit.rest.repos.get({ owner, repo }), `getRepositoryInfo(${owner}/${repo})`);
|
|
133
|
+
const { data: commits } = await withRetry(() => this.octokit.rest.repos.listCommits({ owner, repo, per_page: 1 }), `getCommits(${owner}/${repo})`);
|
|
59
134
|
const lastCommit = commits[0]
|
|
60
135
|
? {
|
|
61
136
|
sha: commits[0].sha,
|
|
@@ -93,12 +168,7 @@ export class GitHubClient {
|
|
|
93
168
|
return cached;
|
|
94
169
|
}
|
|
95
170
|
try {
|
|
96
|
-
const { data } = await this.octokit.rest.repos.getContent({
|
|
97
|
-
owner,
|
|
98
|
-
repo,
|
|
99
|
-
path,
|
|
100
|
-
ref,
|
|
101
|
-
});
|
|
171
|
+
const { data } = await withRetry(() => this.octokit.rest.repos.getContent({ owner, repo, path, ref }), `getFileContent(${owner}/${repo}/${path})`);
|
|
102
172
|
if (Array.isArray(data) || data.type !== "file") {
|
|
103
173
|
return null;
|
|
104
174
|
}
|
|
@@ -133,17 +203,17 @@ export class GitHubClient {
|
|
|
133
203
|
return cached;
|
|
134
204
|
}
|
|
135
205
|
try {
|
|
136
|
-
const { data: refData } = await this.octokit.rest.git.getRef({
|
|
206
|
+
const { data: refData } = await withRetry(() => this.octokit.rest.git.getRef({
|
|
137
207
|
owner,
|
|
138
208
|
repo,
|
|
139
209
|
ref: `heads/${ref || "main"}`,
|
|
140
|
-
});
|
|
141
|
-
const { data: treeData } = await this.octokit.rest.git.getTree({
|
|
210
|
+
}), `getRef(${owner}/${repo})`);
|
|
211
|
+
const { data: treeData } = await withRetry(() => this.octokit.rest.git.getTree({
|
|
142
212
|
owner,
|
|
143
213
|
repo,
|
|
144
214
|
tree_sha: refData.object.sha,
|
|
145
215
|
recursive: "true",
|
|
146
|
-
});
|
|
216
|
+
}), `getTree(${owner}/${repo})`);
|
|
147
217
|
const result = treeData.tree
|
|
148
218
|
.filter((item) => item.type === "blob" && item.path)
|
|
149
219
|
.map((item) => item.path);
|
|
@@ -282,6 +352,48 @@ export class GitHubClient {
|
|
|
282
352
|
return [];
|
|
283
353
|
}
|
|
284
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Get current rate limit status from GitHub API
|
|
357
|
+
*/
|
|
358
|
+
async getRateLimit() {
|
|
359
|
+
try {
|
|
360
|
+
const { data } = await this.octokit.rest.rateLimit.get();
|
|
361
|
+
const rateLimit = {
|
|
362
|
+
limit: data.rate.limit,
|
|
363
|
+
remaining: data.rate.remaining,
|
|
364
|
+
reset: new Date(data.rate.reset * 1000),
|
|
365
|
+
used: data.rate.used,
|
|
366
|
+
};
|
|
367
|
+
// Update the global rate limit tracker
|
|
368
|
+
updateRateLimit(rateLimit);
|
|
369
|
+
return rateLimit;
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
logger.warn("Failed to get rate limit", { error: String(error) });
|
|
373
|
+
// Return defaults if we can't get rate limit
|
|
374
|
+
return {
|
|
375
|
+
limit: 60,
|
|
376
|
+
remaining: 60,
|
|
377
|
+
reset: new Date(Date.now() + 3600000),
|
|
378
|
+
used: 0,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Check if it's safe to make API requests
|
|
384
|
+
*/
|
|
385
|
+
checkRateLimit() {
|
|
386
|
+
const check = shouldProceedWithRequest();
|
|
387
|
+
const status = getRateLimitStatus();
|
|
388
|
+
if (!check.proceed) {
|
|
389
|
+
logger.warn("Rate limit check failed", { reason: check.reason });
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
proceed: check.proceed,
|
|
393
|
+
reason: check.reason,
|
|
394
|
+
status,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
285
397
|
}
|
|
286
398
|
export const githubClient = new GitHubClient();
|
|
287
399
|
//# sourceMappingURL=github.js.map
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
-
import { logger } from "./utils/index.js";
|
|
4
|
+
import { logger, formatErrorResponse } from "./utils/index.js";
|
|
5
5
|
import { vectorStore } from "./db/index.js";
|
|
6
6
|
import { allTools } from "./tools/index.js";
|
|
7
7
|
import { allResources, getDocumentation, getCode, getSchema, } from "./resources/index.js";
|
|
@@ -57,11 +57,19 @@ function registerToolHandlers(server) {
|
|
|
57
57
|
logger.info(`Tool called: ${name}`, { args });
|
|
58
58
|
const tool = allTools.find((t) => t.name === name);
|
|
59
59
|
if (!tool) {
|
|
60
|
+
const availableTools = allTools
|
|
61
|
+
.map((t) => t.name)
|
|
62
|
+
.slice(0, 5)
|
|
63
|
+
.join(", ");
|
|
60
64
|
return {
|
|
61
65
|
content: [
|
|
62
66
|
{
|
|
63
67
|
type: "text",
|
|
64
|
-
text:
|
|
68
|
+
text: JSON.stringify({
|
|
69
|
+
error: `Unknown tool: ${name}`,
|
|
70
|
+
suggestion: `Available tools include: ${availableTools}...`,
|
|
71
|
+
hint: "Use ListTools to see all available tools",
|
|
72
|
+
}, null, 2),
|
|
65
73
|
},
|
|
66
74
|
],
|
|
67
75
|
isError: true,
|
|
@@ -80,11 +88,12 @@ function registerToolHandlers(server) {
|
|
|
80
88
|
}
|
|
81
89
|
catch (error) {
|
|
82
90
|
logger.error(`Tool error: ${name}`, { error: String(error) });
|
|
91
|
+
const errorResponse = formatErrorResponse(error, `tool:${name}`);
|
|
83
92
|
return {
|
|
84
93
|
content: [
|
|
85
94
|
{
|
|
86
95
|
type: "text",
|
|
87
|
-
text:
|
|
96
|
+
text: JSON.stringify(errorResponse, null, 2),
|
|
88
97
|
},
|
|
89
98
|
],
|
|
90
99
|
isError: true,
|
|
@@ -129,12 +138,24 @@ function registerResourceHandlers(server) {
|
|
|
129
138
|
mimeType = "application/json";
|
|
130
139
|
}
|
|
131
140
|
if (!content) {
|
|
141
|
+
const resourceTypes = [
|
|
142
|
+
"midnight://docs/",
|
|
143
|
+
"midnight://code/",
|
|
144
|
+
"midnight://schema/",
|
|
145
|
+
];
|
|
146
|
+
const validPrefix = resourceTypes.find((p) => uri.startsWith(p));
|
|
132
147
|
return {
|
|
133
148
|
contents: [
|
|
134
149
|
{
|
|
135
150
|
uri,
|
|
136
|
-
mimeType: "
|
|
137
|
-
text:
|
|
151
|
+
mimeType: "application/json",
|
|
152
|
+
text: JSON.stringify({
|
|
153
|
+
error: `Resource not found: ${uri}`,
|
|
154
|
+
suggestion: validPrefix
|
|
155
|
+
? `Check the resource path after '${validPrefix}'`
|
|
156
|
+
: `Valid resource prefixes: ${resourceTypes.join(", ")}`,
|
|
157
|
+
hint: "Use ListResources to see all available resources",
|
|
158
|
+
}, null, 2),
|
|
138
159
|
},
|
|
139
160
|
],
|
|
140
161
|
};
|
|
@@ -151,12 +172,13 @@ function registerResourceHandlers(server) {
|
|
|
151
172
|
}
|
|
152
173
|
catch (error) {
|
|
153
174
|
logger.error(`Resource error: ${uri}`, { error: String(error) });
|
|
175
|
+
const errorResponse = formatErrorResponse(error, `resource:${uri}`);
|
|
154
176
|
return {
|
|
155
177
|
contents: [
|
|
156
178
|
{
|
|
157
179
|
uri,
|
|
158
|
-
mimeType: "
|
|
159
|
-
text:
|
|
180
|
+
mimeType: "application/json",
|
|
181
|
+
text: JSON.stringify(errorResponse, null, 2),
|
|
160
182
|
},
|
|
161
183
|
],
|
|
162
184
|
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health check and diagnostic tools for MCP server
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export declare const HealthCheckInputSchema: z.ZodObject<{
|
|
6
|
+
detailed: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
detailed: boolean;
|
|
9
|
+
}, {
|
|
10
|
+
detailed?: boolean | undefined;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const GetStatusInputSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
13
|
+
export type HealthCheckInput = z.infer<typeof HealthCheckInputSchema>;
|
|
14
|
+
export type GetStatusInput = z.infer<typeof GetStatusInputSchema>;
|
|
15
|
+
/**
|
|
16
|
+
* Perform health check on the MCP server
|
|
17
|
+
*/
|
|
18
|
+
export declare function healthCheck(input: HealthCheckInput): Promise<{
|
|
19
|
+
rateLimit: string;
|
|
20
|
+
cacheStats: {
|
|
21
|
+
search: import("../utils/cache.js").CacheStats;
|
|
22
|
+
file: import("../utils/cache.js").CacheStats;
|
|
23
|
+
metadata: import("../utils/cache.js").CacheStats;
|
|
24
|
+
};
|
|
25
|
+
status: "healthy" | "degraded" | "unhealthy";
|
|
26
|
+
timestamp: string;
|
|
27
|
+
version: string;
|
|
28
|
+
uptime: number;
|
|
29
|
+
checks: {
|
|
30
|
+
name: string;
|
|
31
|
+
status: "pass" | "warn" | "fail";
|
|
32
|
+
message?: string;
|
|
33
|
+
latency?: number;
|
|
34
|
+
}[];
|
|
35
|
+
} | {
|
|
36
|
+
rateLimit: string;
|
|
37
|
+
status: "healthy" | "degraded" | "unhealthy";
|
|
38
|
+
version: string;
|
|
39
|
+
timestamp: string;
|
|
40
|
+
uptime: number;
|
|
41
|
+
checks: {
|
|
42
|
+
name: string;
|
|
43
|
+
status: "pass";
|
|
44
|
+
}[];
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Get current server status and statistics
|
|
48
|
+
*/
|
|
49
|
+
export declare function getStatus(_input: GetStatusInput): Promise<{
|
|
50
|
+
server: string;
|
|
51
|
+
status: string;
|
|
52
|
+
timestamp: string;
|
|
53
|
+
rateLimit: {
|
|
54
|
+
remaining: number;
|
|
55
|
+
limit: number;
|
|
56
|
+
percentUsed: number;
|
|
57
|
+
status: string;
|
|
58
|
+
message: string;
|
|
59
|
+
};
|
|
60
|
+
cache: {
|
|
61
|
+
search: import("../utils/cache.js").CacheStats;
|
|
62
|
+
file: import("../utils/cache.js").CacheStats;
|
|
63
|
+
metadata: import("../utils/cache.js").CacheStats;
|
|
64
|
+
};
|
|
65
|
+
}>;
|
|
66
|
+
export declare const healthTools: ({
|
|
67
|
+
name: string;
|
|
68
|
+
description: string;
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: "object";
|
|
71
|
+
properties: {
|
|
72
|
+
detailed: {
|
|
73
|
+
type: string;
|
|
74
|
+
description: string;
|
|
75
|
+
default: boolean;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
handler: typeof healthCheck;
|
|
80
|
+
} | {
|
|
81
|
+
name: string;
|
|
82
|
+
description: string;
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object";
|
|
85
|
+
properties: {
|
|
86
|
+
detailed?: undefined;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
handler: typeof getStatus;
|
|
90
|
+
})[];
|
|
91
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health check and diagnostic tools for MCP server
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { getHealthStatus, getQuickHealthStatus, getRateLimitStatus, formatRateLimitStatus, } from "../utils/index.js";
|
|
6
|
+
import { searchCache, fileCache, metadataCache } from "../utils/cache.js";
|
|
7
|
+
// Schema definitions
|
|
8
|
+
export const HealthCheckInputSchema = z.object({
|
|
9
|
+
detailed: z
|
|
10
|
+
.boolean()
|
|
11
|
+
.optional()
|
|
12
|
+
.default(false)
|
|
13
|
+
.describe("Include detailed checks (slower but more comprehensive)"),
|
|
14
|
+
});
|
|
15
|
+
export const GetStatusInputSchema = z.object({});
|
|
16
|
+
/**
|
|
17
|
+
* Perform health check on the MCP server
|
|
18
|
+
*/
|
|
19
|
+
export async function healthCheck(input) {
|
|
20
|
+
if (input.detailed) {
|
|
21
|
+
const status = await getHealthStatus();
|
|
22
|
+
return {
|
|
23
|
+
...status,
|
|
24
|
+
rateLimit: formatRateLimitStatus(),
|
|
25
|
+
cacheStats: {
|
|
26
|
+
search: searchCache.getStats(),
|
|
27
|
+
file: fileCache.getStats(),
|
|
28
|
+
metadata: metadataCache.getStats(),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...getQuickHealthStatus(),
|
|
34
|
+
rateLimit: formatRateLimitStatus(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get current server status and statistics
|
|
39
|
+
*/
|
|
40
|
+
export async function getStatus(_input) {
|
|
41
|
+
const rateLimitStatus = getRateLimitStatus();
|
|
42
|
+
return {
|
|
43
|
+
server: "midnight-mcp",
|
|
44
|
+
status: "running",
|
|
45
|
+
timestamp: new Date().toISOString(),
|
|
46
|
+
rateLimit: {
|
|
47
|
+
remaining: rateLimitStatus.remaining,
|
|
48
|
+
limit: rateLimitStatus.limit,
|
|
49
|
+
percentUsed: rateLimitStatus.percentUsed,
|
|
50
|
+
status: rateLimitStatus.isLimited
|
|
51
|
+
? "limited"
|
|
52
|
+
: rateLimitStatus.isWarning
|
|
53
|
+
? "warning"
|
|
54
|
+
: "ok",
|
|
55
|
+
message: rateLimitStatus.message,
|
|
56
|
+
},
|
|
57
|
+
cache: {
|
|
58
|
+
search: searchCache.getStats(),
|
|
59
|
+
file: fileCache.getStats(),
|
|
60
|
+
metadata: metadataCache.getStats(),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Tool definitions for MCP server
|
|
65
|
+
export const healthTools = [
|
|
66
|
+
{
|
|
67
|
+
name: "midnight-health-check",
|
|
68
|
+
description: "Check the health status of the Midnight MCP server. Returns server status, API connectivity, and resource availability.",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: "object",
|
|
71
|
+
properties: {
|
|
72
|
+
detailed: {
|
|
73
|
+
type: "boolean",
|
|
74
|
+
description: "Include detailed checks including GitHub API and vector store status (slower)",
|
|
75
|
+
default: false,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
handler: healthCheck,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "midnight-get-status",
|
|
83
|
+
description: "Get current server status including rate limits and cache statistics. Quick status check without external API calls.",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {},
|
|
87
|
+
},
|
|
88
|
+
handler: getStatus,
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
//# sourceMappingURL=health.js.map
|