@code-context-engine/mcp 0.1.4 → 0.1.6
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 +1 -0
- package/dist/codebaseContext.d.ts +2 -0
- package/dist/codebaseContext.js +5 -1
- package/dist/remoteClient.d.ts +1 -0
- package/dist/remoteClient.js +5 -1
- package/dist/remoteMcpServer.js +56 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ No `CONTEXT_ENGINE_REMOTE_URL` needed — defaults to `https://api.codesay.ai`.
|
|
|
46
46
|
| `CONTEXT_ENGINE_REMOTE_API_KEY` | yes | — | User API key (`cek_…`) from the web console |
|
|
47
47
|
| `CONTEXT_ENGINE_REMOTE_URL` | no | `https://api.codesay.ai` | Remote HTTP service base URL |
|
|
48
48
|
| `CONTEXT_ENGINE_REMOTE_MAX_FILE_BYTES` | no | `1048576` | Skip local files larger than this when syncing |
|
|
49
|
+
| `CONTEXT_ENGINE_REMOTE_TIMEOUT_MS` | no | `120000` | Per-request HTTP timeout (ms); raise for first sync of large repos |
|
|
49
50
|
|
|
50
51
|
## Tool: `codebase_context`
|
|
51
52
|
|
|
@@ -14,6 +14,8 @@ export interface CodebaseContextInput {
|
|
|
14
14
|
/** 本地文件发现的单文件大小上限(字节),应与远程服务的 CONTEXT_ENGINE_REMOTE_MAX_FILE_BYTES 一致——
|
|
15
15
|
* 否则本地判断"该传"的文件会在上传时被服务端按 413 拒绝。 */
|
|
16
16
|
maxFileBytes?: number;
|
|
17
|
+
/** 单次远程 HTTP 请求超时(毫秒),默认 120s。首次同步大仓库可通过 MCP env 调大。 */
|
|
18
|
+
timeoutMs?: number;
|
|
17
19
|
feedback?: CodebaseContextFeedbackInput;
|
|
18
20
|
}
|
|
19
21
|
export interface CodebaseContextFeedbackInput {
|
package/dist/codebaseContext.js
CHANGED
|
@@ -32,7 +32,11 @@ export async function runCodebaseContext(input) {
|
|
|
32
32
|
if (!isAbsolute(input.path)) {
|
|
33
33
|
throw new CodebaseContextInputError('Use an absolute local repository path.');
|
|
34
34
|
}
|
|
35
|
-
const client = new RemoteApiClient({
|
|
35
|
+
const client = new RemoteApiClient({
|
|
36
|
+
baseUrl: input.baseUrl,
|
|
37
|
+
apiKey: input.apiKey,
|
|
38
|
+
timeoutMs: input.timeoutMs,
|
|
39
|
+
});
|
|
36
40
|
const sync = await syncLocalRepo(client, input.path, {
|
|
37
41
|
ignoreGlobs: input.ignoreGlobs,
|
|
38
42
|
forceIndex: input.forceIndex,
|
package/dist/remoteClient.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ export interface RemoteFeedbackEvalResult {
|
|
|
79
79
|
on: EvalReport;
|
|
80
80
|
quality: FeedbackQualitySummary;
|
|
81
81
|
}
|
|
82
|
+
export declare function resolveRemoteTimeoutMs(env?: NodeJS.ProcessEnv): number;
|
|
82
83
|
export declare class RemoteApiClient {
|
|
83
84
|
private opts;
|
|
84
85
|
private baseUrl;
|
package/dist/remoteClient.js
CHANGED
|
@@ -107,7 +107,11 @@ function mapActiveTrainingModel(r) {
|
|
|
107
107
|
activatedBy: r.activated_by,
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
|
-
const DEFAULT_TIMEOUT_MS =
|
|
110
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
111
|
+
export function resolveRemoteTimeoutMs(env = process.env) {
|
|
112
|
+
const n = Number(env.CONTEXT_ENGINE_REMOTE_TIMEOUT_MS ?? DEFAULT_TIMEOUT_MS);
|
|
113
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_TIMEOUT_MS;
|
|
114
|
+
}
|
|
111
115
|
export class RemoteApiClient {
|
|
112
116
|
opts;
|
|
113
117
|
baseUrl;
|
package/dist/remoteMcpServer.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
+
import { realpathSync } from 'node:fs';
|
|
5
6
|
import { isAbsolute } from 'node:path';
|
|
6
|
-
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
8
|
import { loadConfig, resolveRemoteUrl } from '@code-context-engine/shared';
|
|
8
9
|
import { CodebaseContextInputError, runCodebaseContext } from './codebaseContext.js';
|
|
9
|
-
import { RemoteApiError, remoteQuotaErrorMessage } from './remoteClient.js';
|
|
10
|
-
import { SyncLocalRepoInputError } from './syncLocalRepo.js';
|
|
10
|
+
import { RemoteApiError, remoteQuotaErrorMessage, resolveRemoteTimeoutMs } from './remoteClient.js';
|
|
11
|
+
import { RemoteIndexError, SyncLocalRepoInputError } from './syncLocalRepo.js';
|
|
11
12
|
const ok = (data) => ({
|
|
12
13
|
content: [{ type: 'text', text: JSON.stringify(data) }],
|
|
13
14
|
structuredContent: data,
|
|
@@ -74,10 +75,42 @@ const codebaseContextOutputSchema = z.object({
|
|
|
74
75
|
}).strict().optional(),
|
|
75
76
|
}).strict(),
|
|
76
77
|
}).strict();
|
|
78
|
+
function isTimeoutError(err) {
|
|
79
|
+
if (!err || typeof err !== 'object')
|
|
80
|
+
return false;
|
|
81
|
+
const name = err.name;
|
|
82
|
+
return name === 'TimeoutError' || name === 'AbortError';
|
|
83
|
+
}
|
|
84
|
+
function isNetworkError(err) {
|
|
85
|
+
if (!(err instanceof TypeError))
|
|
86
|
+
return false;
|
|
87
|
+
const cause = err.cause;
|
|
88
|
+
if (cause && typeof cause === 'object' && 'code' in cause) {
|
|
89
|
+
const code = cause.code;
|
|
90
|
+
return typeof code === 'string' && (code === 'ENOTFOUND'
|
|
91
|
+
|| code === 'ECONNREFUSED'
|
|
92
|
+
|| code === 'ECONNRESET'
|
|
93
|
+
|| code === 'ETIMEDOUT'
|
|
94
|
+
|| code === 'UND_ERR_CONNECT_TIMEOUT');
|
|
95
|
+
}
|
|
96
|
+
return /fetch failed/i.test(err.message);
|
|
97
|
+
}
|
|
77
98
|
function publicRemoteErrorMessage(err) {
|
|
78
99
|
if (err instanceof CodebaseContextInputError || err instanceof SyncLocalRepoInputError) {
|
|
79
100
|
return `codebase_context input invalid: ${err.message}`;
|
|
80
101
|
}
|
|
102
|
+
if (err instanceof RemoteIndexError) {
|
|
103
|
+
return `Remote Context Engine request failed: indexing_failed. ${err.message.replace(/^Remote indexing failed:\s*/i, '')}`;
|
|
104
|
+
}
|
|
105
|
+
if (isTimeoutError(err)) {
|
|
106
|
+
return 'Remote Context Engine request failed: request_timeout. '
|
|
107
|
+
+ 'The remote service did not respond in time (often during the first sync of a large repository). '
|
|
108
|
+
+ 'Retry the same query, or raise CONTEXT_ENGINE_REMOTE_TIMEOUT_MS in MCP env.';
|
|
109
|
+
}
|
|
110
|
+
if (isNetworkError(err)) {
|
|
111
|
+
return 'Remote Context Engine request failed: network_error. '
|
|
112
|
+
+ 'Could not reach the remote service. Check network access to api.codesay.ai and retry.';
|
|
113
|
+
}
|
|
81
114
|
if (err instanceof RemoteApiError) {
|
|
82
115
|
if (err.status === 401 || err.status === 403) {
|
|
83
116
|
return 'Remote Context Engine request failed: authentication_failed. Check the local MCP API key configuration.';
|
|
@@ -85,9 +118,15 @@ function publicRemoteErrorMessage(err) {
|
|
|
85
118
|
if (err.status === 402) {
|
|
86
119
|
return remoteQuotaErrorMessage(err);
|
|
87
120
|
}
|
|
121
|
+
if (err.status === 404) {
|
|
122
|
+
return 'Remote Context Engine request failed: not_found. The remote repository or endpoint is missing. Retry after sync completes.';
|
|
123
|
+
}
|
|
88
124
|
if (err.status === 413) {
|
|
89
125
|
return 'Remote Context Engine request failed: payload_too_large. Reduce ignored files or split the repository, then retry.';
|
|
90
126
|
}
|
|
127
|
+
if (err.status === 429) {
|
|
128
|
+
return 'Remote Context Engine request failed: rate_limited. Wait a moment and retry.';
|
|
129
|
+
}
|
|
91
130
|
if (err.status === 400) {
|
|
92
131
|
return 'Remote Context Engine request failed: invalid_request. Check the local MCP input and repository sync settings.';
|
|
93
132
|
}
|
|
@@ -112,7 +151,7 @@ export function createRemoteMcpServer() {
|
|
|
112
151
|
},
|
|
113
152
|
}, async ({ path, query, token_budget, mode, ext_filter, path_prefix, max_files, feedback, }) => {
|
|
114
153
|
const baseUrl = resolveRemoteUrl();
|
|
115
|
-
const key = cfg.remote.apiKey;
|
|
154
|
+
const key = process.env.CONTEXT_ENGINE_REMOTE_API_KEY?.trim() || cfg.remote.apiKey;
|
|
116
155
|
if (!key)
|
|
117
156
|
throw new Error('Remote Context Engine is not configured. Contact the server operator.');
|
|
118
157
|
try {
|
|
@@ -127,6 +166,7 @@ export function createRemoteMcpServer() {
|
|
|
127
166
|
pathPrefix: path_prefix,
|
|
128
167
|
maxFiles: max_files,
|
|
129
168
|
maxFileBytes: cfg.remote.maxFileBytes,
|
|
169
|
+
timeoutMs: resolveRemoteTimeoutMs(),
|
|
130
170
|
feedback: feedback ? {
|
|
131
171
|
rating: feedback.rating,
|
|
132
172
|
requestId: feedback.request_id,
|
|
@@ -143,7 +183,18 @@ export function createRemoteMcpServer() {
|
|
|
143
183
|
async function main() {
|
|
144
184
|
await createRemoteMcpServer().connect(new StdioServerTransport());
|
|
145
185
|
}
|
|
146
|
-
|
|
186
|
+
function isExecutedDirectly() {
|
|
187
|
+
const entry = process.argv[1];
|
|
188
|
+
if (!entry)
|
|
189
|
+
return false;
|
|
190
|
+
try {
|
|
191
|
+
return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entry);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return import.meta.url === pathToFileURL(entry).href;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (isExecutedDirectly()) {
|
|
147
198
|
main().catch((err) => {
|
|
148
199
|
console.error(err);
|
|
149
200
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-context-engine/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "MCP server that gives Claude Code, Cursor, and any MCP client real codebase context without burning tokens — incremental sync, hybrid dense+sparse+symbol-graph retrieval, token-budgeted context packs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://codesay.ai",
|