@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 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 {
@@ -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({ baseUrl: input.baseUrl, apiKey: input.apiKey });
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,
@@ -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;
@@ -107,7 +107,11 @@ function mapActiveTrainingModel(r) {
107
107
  activatedBy: r.activated_by,
108
108
  };
109
109
  }
110
- const DEFAULT_TIMEOUT_MS = 30_000;
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;
@@ -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
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
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.4",
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",