ctxman 3.0.0 → 3.1.0
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/.ctxman/llm-profiles.json +1067 -39
- package/.ctxman/profiles/backend-team.json +13 -0
- package/.ctxman/profiles/default.json +12 -0
- package/.ctxman/profiles/devops-team.json +21 -0
- package/.ctxman/profiles/frontend-team.json +20 -0
- package/.ctxman/templates/bug-fix.json +17 -0
- package/.ctxman/templates/code-review.json +9 -0
- package/.ctxman/templates/documentation.json +9 -0
- package/.ctxman/templates/feature.json +9 -0
- package/.ctxman/templates/full-context.json +9 -0
- package/.ctxman/templates/refactoring.json +18 -0
- package/.ctxman/templates/testing.json +9 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
- package/LICENSE +21 -0
- package/README.md +380 -136
- package/bin/cli.js +1880 -529
- package/bin/cm-ask.js +18 -13
- package/bin/cm-audit.js +124 -0
- package/bin/cm-count.js +151 -0
- package/bin/cm-gitingest.js +128 -115
- package/bin/cm-map.js +242 -0
- package/bin/cm-mcp.js +293 -0
- package/bin/cm-pack.js +405 -0
- package/bin/cm-policy.js +170 -0
- package/bin/cm-pr.js +248 -0
- package/bin/cm-search.js +178 -0
- package/bin/cm-update.js +128 -128
- package/bin/cm-why.js +197 -0
- package/bin/mcp-server.js +8 -24
- package/ctxman.js +237 -183
- package/examples/README.md +19 -4
- package/examples/github-actions/ctxman-check.yml +49 -0
- package/examples/wizard-profiles/security-audit/profile.json +7 -1
- package/index.d.ts +1422 -0
- package/index.js +55 -31
- package/lib/agents/pr-failure-agent.js +48 -45
- package/lib/analyzers/ai-suggester.js +451 -0
- package/lib/analyzers/dependency-scanner.js +505 -0
- package/lib/analyzers/duplicate-detector.js +264 -0
- package/lib/analyzers/go-method-analyzer.js +151 -110
- package/lib/analyzers/import-tracker.js +431 -0
- package/lib/analyzers/method-analyzer.js +1055 -434
- package/lib/analyzers/performance-dashboard.js +683 -0
- package/lib/analyzers/token-calculator.js +895 -504
- package/lib/analyzers/unused-exports-detector.js +336 -0
- package/lib/api/mcp/install.js +424 -0
- package/lib/api/mcp/prompts.js +281 -281
- package/lib/api/mcp/resources.js +300 -241
- package/lib/api/mcp/sandbox.js +89 -0
- package/lib/api/mcp/server.js +675 -312
- package/lib/api/mcp/start.js +125 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
- package/lib/api/rest/server.js +105 -28
- package/lib/cache/CacheManager.js +9 -17
- package/lib/core/Analyzer.js +16 -10
- package/lib/core/ContextBuilder.js +19 -28
- package/lib/core/Reporter.js +15 -14
- package/lib/core/Scanner.js +81 -12
- package/lib/count/index.js +100 -0
- package/lib/formatters/format-registry.js +394 -372
- package/lib/formatters/gitingest-formatter.js +656 -621
- package/lib/formatters/toon-formatter-v1.3.js +637 -632
- package/lib/formatters/toon-formatter.js +239 -240
- package/lib/integrations/git/BlameTracker.js +4 -4
- package/lib/integrations/git/DiffAnalyzer.js +38 -31
- package/lib/integrations/git/GitClient.js +71 -53
- package/lib/lsp/server.js +311 -0
- package/lib/map/graph.js +299 -0
- package/lib/map/imports.js +233 -0
- package/lib/map/index.js +33 -0
- package/lib/map/rank.js +253 -0
- package/lib/map/render.js +346 -0
- package/lib/map/resolver.js +574 -0
- package/lib/pack/budget.js +111 -0
- package/lib/pack/code-lexer.js +360 -0
- package/lib/pack/compress.js +414 -0
- package/lib/pack/index.js +354 -0
- package/lib/pack/renderers/common.js +134 -0
- package/lib/pack/renderers/index.js +24 -0
- package/lib/pack/renderers/json.js +47 -0
- package/lib/pack/renderers/markdown.js +54 -0
- package/lib/pack/renderers/plain.js +38 -0
- package/lib/pack/renderers/xml.js +85 -0
- package/lib/pack/select.js +177 -0
- package/lib/pack/split.js +182 -0
- package/lib/pack/tree.js +35 -0
- package/lib/parsers/gitignore-parser.js +113 -107
- package/lib/parsers/method-filter-parser.js +50 -33
- package/lib/plugins/ExporterPlugin.js +3 -3
- package/lib/plugins/LanguagePlugin.js +3 -3
- package/lib/plugins/PluginManager.js +7 -13
- package/lib/policy/audit.js +294 -0
- package/lib/policy/enforce.js +125 -0
- package/lib/policy/ignore-files.js +169 -0
- package/lib/policy/index.js +52 -0
- package/lib/policy/policy.js +489 -0
- package/lib/pr/diff-parser.js +267 -0
- package/lib/pr/method-mapper.js +148 -0
- package/lib/pr/pr-context.js +829 -0
- package/lib/pr/pr-git.js +149 -0
- package/lib/pr/pr-renderer.js +644 -0
- package/lib/pr/related-tests.js +296 -0
- package/lib/rag/DataSourcePlugin.js +5 -5
- package/lib/rag/EmbeddingProvider.js +19 -8
- package/lib/rag/Indexer.js +14 -5
- package/lib/rag/VectorStoreFactory.js +3 -3
- package/lib/rag/sources/FileDataSource.js +6 -6
- package/lib/rag/stores/BaseVectorStore.js +5 -5
- package/lib/rag/stores/ChromaDBStore.js +10 -10
- package/lib/rag/stores/LanceDBStore.js +29 -19
- package/lib/rag/stores/PineconeStore.js +11 -9
- package/lib/search/bm25.js +433 -0
- package/lib/search/code-units.js +201 -0
- package/lib/search/symbols.js +189 -0
- package/lib/ui/dashboard.js +120 -89
- package/lib/ui/index.js +1 -6
- package/lib/ui/init-wizard-ui.js +550 -0
- package/lib/ui/progress-bar.js +94 -88
- package/lib/ui/select-input.js +40 -32
- package/lib/ui/template-selector.js +99 -0
- package/lib/ui/wizard.js +374 -329
- package/lib/utils/claude-token-counter.js +151 -0
- package/lib/utils/cli-output.js +17 -0
- package/lib/utils/clipboard-utils.js +49 -50
- package/lib/utils/config-utils.js +81 -66
- package/lib/utils/context-versioning.js +532 -0
- package/lib/utils/error-handler.js +112 -112
- package/lib/utils/file-utils.js +153 -69
- package/lib/utils/format-converter.js +356 -283
- package/lib/utils/git-utils.js +366 -294
- package/lib/utils/llm-cost-estimator.js +281 -0
- package/lib/utils/llm-detector.js +242 -78
- package/lib/utils/logger.js +243 -237
- package/lib/utils/messages.js +355 -0
- package/lib/utils/multi-repo-manager.js +364 -0
- package/lib/utils/profile-manager.js +380 -0
- package/lib/utils/safe-read.js +61 -0
- package/lib/utils/secret-redactor.js +61 -0
- package/lib/utils/snapshot-manager.js +679 -0
- package/lib/utils/template-manager.js +378 -0
- package/lib/utils/token-utils.js +201 -170
- package/lib/utils/tokenizer-adapter.js +421 -377
- package/lib/utils/updater.js +539 -506
- package/lib/watch/FileWatcher.js +12 -8
- package/lib/watch/IncrementalAnalyzer.js +7 -7
- package/lib/why/coupling.js +81 -0
- package/lib/why/errors.js +23 -0
- package/lib/why/git.js +169 -0
- package/lib/why/history.js +108 -0
- package/lib/why/index.js +720 -0
- package/lib/why/ownership.js +79 -0
- package/lib/why/paths.js +207 -0
- package/lib/why/references.js +259 -0
- package/lib/why/render.js +722 -0
- package/lib/why/search.js +358 -0
- package/lib/why/target.js +389 -0
- package/lib/wizards/config-generator.js +255 -0
- package/lib/wizards/project-detector.js +571 -0
- package/package.json +44 -10
- package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
- package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
package/lib/api/mcp/server.js
CHANGED
|
@@ -1,339 +1,702 @@
|
|
|
1
|
-
import { Server } from
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { ResourceProvider } from
|
|
18
|
-
import { PromptProvider } from
|
|
19
|
-
import
|
|
20
|
-
import
|
|
4
|
+
CallToolRequestSchema,
|
|
5
|
+
ListToolsRequestSchema,
|
|
6
|
+
ListResourcesRequestSchema,
|
|
7
|
+
ReadResourceRequestSchema,
|
|
8
|
+
ListResourceTemplatesRequestSchema,
|
|
9
|
+
ListPromptsRequestSchema,
|
|
10
|
+
GetPromptRequestSchema,
|
|
11
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
12
|
+
import { Analyzer } from '../../core/Analyzer.js';
|
|
13
|
+
import { ContextBuilder } from '../../core/ContextBuilder.js';
|
|
14
|
+
import { Scanner } from '../../core/Scanner.js';
|
|
15
|
+
import { GitClient, isValidGitRef } from '../../integrations/git/GitClient.js';
|
|
16
|
+
import { renderRepoMap } from '../../map/index.js';
|
|
17
|
+
import { ResourceProvider } from './resources.js';
|
|
18
|
+
import { PromptProvider } from './prompts.js';
|
|
19
|
+
import { buildPrContext } from '../../pr/pr-context.js';
|
|
20
|
+
import { resolveRootDir, resolveSandboxPath } from './sandbox.js';
|
|
21
|
+
import { searchProject, DEFAULT_LIMIT, DEFAULT_MAX_TOKENS } from '../../search/bm25.js';
|
|
22
|
+
import { getSymbol } from '../../search/symbols.js';
|
|
23
|
+
import { countForModel } from '../../count/index.js';
|
|
24
|
+
import { explainHistory } from '../../why/index.js';
|
|
25
|
+
import { packContext, describePack, TOOL_DEFAULT_BUDGET } from '../../pack/index.js';
|
|
26
|
+
import { loadPolicy, guardExport } from '../../policy/index.js';
|
|
27
|
+
import fs from 'fs';
|
|
28
|
+
import path from 'path';
|
|
29
|
+
import { createRequire } from 'module';
|
|
30
|
+
|
|
31
|
+
const { version } = createRequire(import.meta.url)('../../../package.json');
|
|
32
|
+
|
|
33
|
+
// Tools whose `path` may also name a single file
|
|
34
|
+
const FILE_PATH_TOOLS = new Set(['list_methods']);
|
|
21
35
|
|
|
22
36
|
class MCPServer {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
/**
|
|
38
|
+
* @param {Object} [options]
|
|
39
|
+
* @param {string} [options.root] - Confine every tool and prompt path to this directory
|
|
40
|
+
*/
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.root = options.root ? resolveRootDir(options.root) : null;
|
|
43
|
+
this.server = new Server(
|
|
44
|
+
{
|
|
45
|
+
name: 'ctxman',
|
|
46
|
+
version,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
// No subscribe or listChanged: the server has no subscription handler and sends no
|
|
50
|
+
// change notifications, so clients must not wait for them
|
|
51
|
+
capabilities: {
|
|
52
|
+
tools: {},
|
|
53
|
+
resources: {},
|
|
54
|
+
prompts: {},
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
this.analyzer = new Analyzer();
|
|
60
|
+
this.contextBuilder = new ContextBuilder();
|
|
61
|
+
this.resourceProvider = new ResourceProvider(this.root ?? undefined);
|
|
62
|
+
this.promptProvider = new PromptProvider(this.analyzer, this.contextBuilder);
|
|
63
|
+
this.setupHandlers();
|
|
64
|
+
this.setupErrorHandling();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setupHandlers() {
|
|
68
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
69
|
+
return {
|
|
70
|
+
tools: [
|
|
71
|
+
{
|
|
72
|
+
name: 'analyze_codebase',
|
|
73
|
+
description: 'Analyze the codebase and return statistics and token counts',
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
path: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
description: 'Absolute path to the project root',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
required: ['path'],
|
|
28
83
|
},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'generate_context',
|
|
87
|
+
description: 'Generate optimized context for LLMs from the codebase',
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: 'object',
|
|
90
|
+
properties: {
|
|
91
|
+
path: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
description: 'Absolute path to the project root',
|
|
39
94
|
},
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
95
|
+
template: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
description: 'Context template (bug-fix, feature, etc.)',
|
|
98
|
+
enum: ['bug-fix', 'feature', 'review', 'refactor'],
|
|
99
|
+
},
|
|
100
|
+
maxTokens: {
|
|
101
|
+
type: 'number',
|
|
102
|
+
description: 'Maximum tokens for the context',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
required: ['path'],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'git_diff',
|
|
110
|
+
description: 'Analyze git changes and return diff context',
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
path: { type: 'string', description: 'Project root' },
|
|
115
|
+
branch: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
description: 'Branch to compare against (default: main)',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: ['path'],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'search_code',
|
|
125
|
+
description:
|
|
126
|
+
'Search source lines by regular expression (default) or plain substring ("semantic" is currently a substring match). "ranked" returns the methods/functions (or 40-line windows) that best match the query words by BM25, with their source.',
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {
|
|
130
|
+
path: { type: 'string', description: 'Project root' },
|
|
131
|
+
query: { type: 'string', description: 'Search query' },
|
|
132
|
+
type: { type: 'string', enum: ['regex', 'semantic', 'ranked'], default: 'regex' },
|
|
133
|
+
limit: {
|
|
134
|
+
type: 'number',
|
|
135
|
+
description: `ranked: maximum number of chunks (default ${DEFAULT_LIMIT})`,
|
|
136
|
+
},
|
|
137
|
+
maxTokens: {
|
|
138
|
+
type: 'number',
|
|
139
|
+
description: `ranked: token budget for the returned source (default ${DEFAULT_MAX_TOKENS})`,
|
|
140
|
+
},
|
|
141
|
+
redact: {
|
|
142
|
+
type: 'boolean',
|
|
143
|
+
description: 'ranked: redact secrets in the returned source (default true)',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
required: ['path', 'query'],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'list_methods',
|
|
151
|
+
description: 'List all methods/functions in a given file or project',
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {
|
|
155
|
+
path: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
description: 'Absolute path to the project root or a specific file',
|
|
158
|
+
},
|
|
159
|
+
file: {
|
|
160
|
+
type: 'string',
|
|
161
|
+
description: 'Optional: Relative path to a specific file within the project',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
required: ['path'],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'repo_map',
|
|
169
|
+
description:
|
|
170
|
+
'Ranked repository map: the most important files (PageRank over the import graph, git change frequency, symbol references) with one line per function signature, trimmed to a token budget',
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: 'object',
|
|
173
|
+
properties: {
|
|
174
|
+
path: { type: 'string', description: 'Absolute path to the project root' },
|
|
175
|
+
tokens: {
|
|
176
|
+
type: 'number',
|
|
177
|
+
description: 'Token budget for the map (default 2048)',
|
|
178
|
+
},
|
|
179
|
+
focus: {
|
|
180
|
+
type: 'array',
|
|
181
|
+
items: { type: 'string' },
|
|
182
|
+
description:
|
|
183
|
+
'Project-relative files or folders to rank higher, with the files next to them in the import graph',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
required: ['path'],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: 'pr_context',
|
|
191
|
+
description:
|
|
192
|
+
'Review context for a branch: summary, commit log, changed files, full source of changed methods, unified diff and related tests (secrets redacted)',
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
path: { type: 'string', description: 'Project directory inside a git work tree' },
|
|
197
|
+
base: {
|
|
198
|
+
type: 'string',
|
|
199
|
+
description:
|
|
200
|
+
'Base branch or commit (default: origin/HEAD, else main, else master)',
|
|
201
|
+
},
|
|
202
|
+
head: { type: 'string', description: 'Head branch or commit (default: HEAD)' },
|
|
203
|
+
budget: { type: 'number', description: 'Maximum tokens of the result' },
|
|
204
|
+
style: { type: 'string', enum: ['markdown', 'xml', 'json'], default: 'markdown' },
|
|
205
|
+
},
|
|
206
|
+
required: ['path'],
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'get_symbol',
|
|
211
|
+
description:
|
|
212
|
+
'Full source of a method, function or class by name, with file and line range. Exact matches first, then case-insensitive; similar names are suggested when nothing matches. "Class.method" narrows to methods of a class.',
|
|
213
|
+
inputSchema: {
|
|
214
|
+
type: 'object',
|
|
215
|
+
properties: {
|
|
216
|
+
path: { type: 'string', description: 'Project root' },
|
|
217
|
+
name: { type: 'string', description: 'Symbol name, or Class.method' },
|
|
218
|
+
file: {
|
|
219
|
+
type: 'string',
|
|
220
|
+
description: 'Optional: only look in this project-relative file',
|
|
221
|
+
},
|
|
222
|
+
redact: {
|
|
223
|
+
type: 'boolean',
|
|
224
|
+
description: 'Redact secrets in the source (default true)',
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
required: ['path', 'name'],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: 'count_tokens',
|
|
232
|
+
description:
|
|
233
|
+
"Token count of a project (or of a text) for a model, with the model's context window and whether it fits. Exact for OpenAI models; approximate for models with their own tokenizer (Claude, Gemini, ...). Nothing leaves the machine; for Claude's exact count run `ctxman count --model <id> --api`",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
properties: {
|
|
237
|
+
path: {
|
|
238
|
+
type: 'string',
|
|
239
|
+
description: 'Project root (ignored when text is given)',
|
|
240
|
+
},
|
|
241
|
+
text: { type: 'string', description: 'Count this text instead of a project' },
|
|
242
|
+
model: {
|
|
243
|
+
type: 'string',
|
|
244
|
+
description: 'Model id from `ctxman --list-llms` (default: claude-sonnet-5-5)',
|
|
245
|
+
},
|
|
246
|
+
top: {
|
|
247
|
+
type: 'number',
|
|
248
|
+
description: 'How many of the largest files to list (default 10)',
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'explain_history',
|
|
255
|
+
description:
|
|
256
|
+
'Explain why code looks the way it does, from the local git history (offline): commits that changed a file, symbol or line range with their messages, referenced issues/PRs/tickets, blame ownership, files that change together with it, related docs/ADRs/comments, TODO notes and tests. Returns Markdown.',
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: 'object',
|
|
259
|
+
properties: {
|
|
260
|
+
path: {
|
|
261
|
+
type: 'string',
|
|
262
|
+
description: 'Absolute path to the project root (inside a git repository)',
|
|
263
|
+
},
|
|
264
|
+
target: {
|
|
265
|
+
type: 'string',
|
|
266
|
+
description:
|
|
267
|
+
'File relative to path, optionally with a symbol or line range: "src/cart.js", "src/cart.js:total", "src/cart.js:Cart.total", "src/cart.js#L10-L40"',
|
|
268
|
+
},
|
|
269
|
+
limit: {
|
|
270
|
+
type: 'number',
|
|
271
|
+
description: 'Maximum number of commits (default 20)',
|
|
272
|
+
},
|
|
273
|
+
budget: {
|
|
274
|
+
type: 'number',
|
|
275
|
+
description:
|
|
276
|
+
'Maximum tokens of the result (default 20000); patches, related text, tests, coupling details and older commits are dropped first',
|
|
277
|
+
},
|
|
278
|
+
since: {
|
|
279
|
+
type: 'string',
|
|
280
|
+
description: 'Only commits after this date ("2024-01-01", "6 months ago")',
|
|
281
|
+
},
|
|
282
|
+
patches: {
|
|
283
|
+
type: 'boolean',
|
|
284
|
+
description: "Include each commit's diff of the target (default false)",
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
required: ['path', 'target'],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: 'pack_context',
|
|
292
|
+
description:
|
|
293
|
+
'File contents of a project in one LLM-ready document (directory tree, then every file; Anthropic <documents> XML by default), honouring .gitignore/.contextignore/.contextinclude, with secrets redacted. Trimmed to a token budget (default 20000): the highest-ranked files first, the rest listed with their token counts.',
|
|
294
|
+
inputSchema: {
|
|
295
|
+
type: 'object',
|
|
296
|
+
properties: {
|
|
297
|
+
path: { type: 'string', description: 'Project root' },
|
|
298
|
+
include: {
|
|
299
|
+
type: 'array',
|
|
300
|
+
items: { type: 'string' },
|
|
301
|
+
description: 'Gitignore-style globs: only matching files (e.g. "src/**/*.ts")',
|
|
302
|
+
},
|
|
303
|
+
exclude: {
|
|
304
|
+
type: 'array',
|
|
305
|
+
items: { type: 'string' },
|
|
306
|
+
description: 'Gitignore-style globs: leave matching files out',
|
|
307
|
+
},
|
|
308
|
+
files: {
|
|
309
|
+
type: 'array',
|
|
310
|
+
items: { type: 'string' },
|
|
311
|
+
description: 'Project-relative files to pack instead of scanning the project',
|
|
312
|
+
},
|
|
313
|
+
style: {
|
|
314
|
+
type: 'string',
|
|
315
|
+
enum: ['xml', 'markdown', 'plain', 'json'],
|
|
316
|
+
default: 'xml',
|
|
317
|
+
},
|
|
318
|
+
compress: {
|
|
319
|
+
type: 'boolean',
|
|
320
|
+
description: 'Keep signatures, replace function bodies with a placeholder',
|
|
321
|
+
},
|
|
322
|
+
lineNumbers: { type: 'boolean', description: 'Number every line' },
|
|
323
|
+
tree: {
|
|
324
|
+
type: 'boolean',
|
|
325
|
+
description: 'Directory tree at the top (default true)',
|
|
326
|
+
},
|
|
327
|
+
instructions: {
|
|
328
|
+
type: 'string',
|
|
329
|
+
description: 'Text placed at the end, after the files',
|
|
330
|
+
},
|
|
331
|
+
budget: {
|
|
332
|
+
type: 'number',
|
|
333
|
+
description: `Maximum tokens of the document (default ${TOOL_DEFAULT_BUDGET})`,
|
|
334
|
+
},
|
|
335
|
+
targetModel: {
|
|
336
|
+
type: 'string',
|
|
337
|
+
description: "Count with this model's tokenizer encoding and report the fit",
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
required: ['path'],
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
};
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Resource handlers
|
|
348
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
|
|
349
|
+
const { cursor } = request.params || {};
|
|
350
|
+
return await this.resourceProvider.listResources(cursor);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
354
|
+
const { uri } = request.params;
|
|
355
|
+
return await this.resourceProvider.readResource(uri);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
359
|
+
return {
|
|
360
|
+
resourceTemplates: this.resourceProvider.getResourceTemplates(),
|
|
361
|
+
};
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Prompt handlers
|
|
365
|
+
this.server.setRequestHandler(ListPromptsRequestSchema, async (request) => {
|
|
366
|
+
const { cursor } = request.params || {};
|
|
367
|
+
return this.promptProvider.listPrompts(cursor);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
371
|
+
const { name } = request.params;
|
|
372
|
+
let args = request.params.arguments;
|
|
373
|
+
// With a root, the paths prompts read must lie inside it too
|
|
374
|
+
if (this.root && args) {
|
|
375
|
+
args = { ...args };
|
|
376
|
+
if (args.projectPath !== undefined) {
|
|
377
|
+
args.projectPath = resolveSandboxPath(args.projectPath, {
|
|
378
|
+
root: this.root,
|
|
379
|
+
name: 'projectPath',
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (args.filePath !== undefined) {
|
|
383
|
+
args.filePath = resolveSandboxPath(args.filePath, {
|
|
384
|
+
root: this.root,
|
|
385
|
+
expect: 'file',
|
|
386
|
+
name: 'filePath',
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return await this.promptProvider.getPrompt(name, args);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
394
|
+
const { name } = request.params;
|
|
395
|
+
let args = request.params.arguments ?? {};
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
// `path` must name an existing directory (inside the root, when there is one)
|
|
399
|
+
if (args.path !== undefined || this.root) {
|
|
400
|
+
args = {
|
|
401
|
+
...args,
|
|
402
|
+
path: resolveSandboxPath(args.path, {
|
|
403
|
+
root: this.root,
|
|
404
|
+
expect: FILE_PATH_TOOLS.has(name) ? 'any' : 'directory',
|
|
405
|
+
}),
|
|
406
|
+
};
|
|
407
|
+
}
|
|
50
408
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
description: "Analyze the codebase and return statistics and token counts",
|
|
58
|
-
inputSchema: {
|
|
59
|
-
type: "object",
|
|
60
|
-
properties: {
|
|
61
|
-
path: {
|
|
62
|
-
type: "string",
|
|
63
|
-
description: "Absolute path to the project root",
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
required: ["path"],
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: "generate_context",
|
|
71
|
-
description: "Generate optimized context for LLMs from the codebase",
|
|
72
|
-
inputSchema: {
|
|
73
|
-
type: "object",
|
|
74
|
-
properties: {
|
|
75
|
-
path: {
|
|
76
|
-
type: "string",
|
|
77
|
-
description: "Absolute path to the project root",
|
|
78
|
-
},
|
|
79
|
-
template: {
|
|
80
|
-
type: "string",
|
|
81
|
-
description: "Context template (bug-fix, feature, etc.)",
|
|
82
|
-
enum: ["bug-fix", "feature", "review", "refactor"],
|
|
83
|
-
},
|
|
84
|
-
maxTokens: {
|
|
85
|
-
type: "number",
|
|
86
|
-
description: "Maximum tokens for the context",
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
required: ["path"],
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
name: "git_diff",
|
|
94
|
-
description: "Analyze git changes and return diff context",
|
|
95
|
-
inputSchema: {
|
|
96
|
-
type: "object",
|
|
97
|
-
properties: {
|
|
98
|
-
path: { type: "string", description: "Project root" },
|
|
99
|
-
branch: { type: "string", description: "Branch to compare against (default: main)" }
|
|
100
|
-
},
|
|
101
|
-
required: ["path"]
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
name: "search_code",
|
|
106
|
-
description: "Search for code using regex or semantic search",
|
|
107
|
-
inputSchema: {
|
|
108
|
-
type: "object",
|
|
109
|
-
properties: {
|
|
110
|
-
path: { type: "string", description: "Project root" },
|
|
111
|
-
query: { type: "string", description: "Search query" },
|
|
112
|
-
type: { type: "string", enum: ["regex", "semantic"], default: "regex" }
|
|
113
|
-
},
|
|
114
|
-
required: ["path", "query"]
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: "list_methods",
|
|
119
|
-
description: "List all methods/functions in a given file or project",
|
|
120
|
-
inputSchema: {
|
|
121
|
-
type: "object",
|
|
122
|
-
properties: {
|
|
123
|
-
path: {
|
|
124
|
-
type: "string",
|
|
125
|
-
description: "Absolute path to the project root or a specific file"
|
|
126
|
-
},
|
|
127
|
-
file: {
|
|
128
|
-
type: "string",
|
|
129
|
-
description: "Optional: Relative path to a specific file within the project"
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
required: ["path"]
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
};
|
|
137
|
-
});
|
|
409
|
+
switch (name) {
|
|
410
|
+
case 'analyze_codebase': {
|
|
411
|
+
const projectPath = args.path;
|
|
412
|
+
|
|
413
|
+
// Update resource provider project path
|
|
414
|
+
this.resourceProvider.projectPath = projectPath;
|
|
138
415
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
return await this.resourceProvider.listResources(cursor);
|
|
143
|
-
});
|
|
416
|
+
const scanner = new Scanner(projectPath);
|
|
417
|
+
const files = scanner.scan();
|
|
418
|
+
const analysis = await this.analyzer.analyze(files);
|
|
144
419
|
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
});
|
|
420
|
+
// Cache analysis result as a resource
|
|
421
|
+
const analysisId = Date.now().toString();
|
|
422
|
+
this.resourceProvider.cacheAnalysis(analysisId, analysis);
|
|
149
423
|
|
|
150
|
-
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
151
424
|
return {
|
|
152
|
-
|
|
425
|
+
content: [
|
|
426
|
+
{
|
|
427
|
+
type: 'text',
|
|
428
|
+
text: JSON.stringify(analysis, null, 2),
|
|
429
|
+
},
|
|
430
|
+
],
|
|
153
431
|
};
|
|
154
|
-
|
|
432
|
+
}
|
|
433
|
+
case 'generate_context': {
|
|
434
|
+
const projectPath = args.path;
|
|
155
435
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const { cursor } = request.params || {};
|
|
159
|
-
return this.promptProvider.listPrompts(cursor);
|
|
160
|
-
});
|
|
436
|
+
// Update resource provider project path
|
|
437
|
+
this.resourceProvider.projectPath = projectPath;
|
|
161
438
|
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
439
|
+
const scanner = new Scanner(projectPath);
|
|
440
|
+
const files = scanner.scan();
|
|
441
|
+
const analysis = await this.analyzer.analyze(files);
|
|
442
|
+
|
|
443
|
+
// Configure ContextBuilder based on args
|
|
444
|
+
const builderOptions = {
|
|
445
|
+
targetTokens: args.maxTokens,
|
|
446
|
+
useCase: args.template || 'custom',
|
|
447
|
+
};
|
|
448
|
+
const contextBuilder = new ContextBuilder(builderOptions);
|
|
449
|
+
const context = contextBuilder.build(analysis);
|
|
450
|
+
|
|
451
|
+
// Cache context as a resource
|
|
452
|
+
const templateName = args.template || 'custom';
|
|
453
|
+
const contextText = JSON.stringify(context, null, 2);
|
|
454
|
+
// Export policy: throws PolicyViolationError (an isError result) and logs the export
|
|
455
|
+
guardExport(loadPolicy(projectPath), {
|
|
456
|
+
command: 'mcp generate_context',
|
|
457
|
+
root: projectPath,
|
|
458
|
+
destination: 'mcp',
|
|
459
|
+
format: 'json',
|
|
460
|
+
files: Object.values(context.files || {}).flat(),
|
|
461
|
+
text: contextText,
|
|
462
|
+
});
|
|
463
|
+
this.resourceProvider.cacheContext(templateName, contextText);
|
|
166
464
|
|
|
167
|
-
|
|
168
|
-
|
|
465
|
+
return {
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: 'text',
|
|
469
|
+
text: contextText,
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
case 'git_diff': {
|
|
475
|
+
const projectPath = args.path;
|
|
476
|
+
const gitClient = new GitClient(projectPath);
|
|
477
|
+
const branch = args.branch || 'main';
|
|
478
|
+
|
|
479
|
+
if (!isValidGitRef(branch)) {
|
|
480
|
+
return {
|
|
481
|
+
content: [
|
|
482
|
+
{ type: 'text', text: `Invalid branch or ref: ${JSON.stringify(branch)}` },
|
|
483
|
+
],
|
|
484
|
+
isError: true,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
169
487
|
|
|
488
|
+
let diff;
|
|
170
489
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.resourceProvider.projectPath = projectPath;
|
|
200
|
-
|
|
201
|
-
const scanner = new Scanner(projectPath);
|
|
202
|
-
const files = scanner.scan();
|
|
203
|
-
const analysis = await this.analyzer.analyze(files);
|
|
204
|
-
|
|
205
|
-
// Configure ContextBuilder based on args
|
|
206
|
-
const builderOptions = {
|
|
207
|
-
targetTokens: args.maxTokens,
|
|
208
|
-
useCase: args.template || "custom"
|
|
209
|
-
};
|
|
210
|
-
const contextBuilder = new ContextBuilder(builderOptions);
|
|
211
|
-
const context = contextBuilder.build(analysis);
|
|
212
|
-
|
|
213
|
-
// Cache context as a resource
|
|
214
|
-
const templateName = args.template || "custom";
|
|
215
|
-
const contextText = JSON.stringify(context, null, 2);
|
|
216
|
-
this.resourceProvider.cacheContext(templateName, contextText);
|
|
217
|
-
|
|
218
|
-
return {
|
|
219
|
-
content: [
|
|
220
|
-
{
|
|
221
|
-
type: "text",
|
|
222
|
-
text: contextText,
|
|
223
|
-
}
|
|
224
|
-
]
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
case "git_diff": {
|
|
228
|
-
const projectPath = args.path;
|
|
229
|
-
const gitClient = new GitClient(projectPath);
|
|
230
|
-
const branch = args.branch || "main";
|
|
231
|
-
|
|
232
|
-
let diff;
|
|
233
|
-
try {
|
|
234
|
-
diff = gitClient.exec(`diff ${branch}`);
|
|
235
|
-
} catch (e) {
|
|
236
|
-
diff = `Error executing git diff: ${e.message}`;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return {
|
|
240
|
-
content: [{ type: "text", text: diff || "No changes detected." }]
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
case "search_code": {
|
|
244
|
-
const { path: projectPath, query, type } = args;
|
|
245
|
-
const scanner = new Scanner(projectPath);
|
|
246
|
-
const files = scanner.scan();
|
|
247
|
-
const results = [];
|
|
248
|
-
|
|
249
|
-
for (const file of files) {
|
|
250
|
-
try {
|
|
251
|
-
const content = fs.readFileSync(file.path, "utf-8");
|
|
252
|
-
const lines = content.split("\n");
|
|
253
|
-
|
|
254
|
-
lines.forEach((line, index) => {
|
|
255
|
-
let match = false;
|
|
256
|
-
if (type === "regex") {
|
|
257
|
-
try {
|
|
258
|
-
if (new RegExp(query).test(line)) match = true;
|
|
259
|
-
} catch (e) { /* ignore invalid regex */ }
|
|
260
|
-
} else {
|
|
261
|
-
if (line.includes(query)) match = true;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (match) {
|
|
265
|
-
results.push({
|
|
266
|
-
file: file.relativePath,
|
|
267
|
-
line: index + 1,
|
|
268
|
-
content: line.trim()
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
} catch (e) {
|
|
273
|
-
// Skip read errors
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return {
|
|
278
|
-
content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
case "list_methods": {
|
|
282
|
-
const projectPath = args.path;
|
|
283
|
-
const scanner = new Scanner(projectPath);
|
|
284
|
-
const files = scanner.scan();
|
|
285
|
-
|
|
286
|
-
// Use Analyzer with methodLevel: true
|
|
287
|
-
const methodAnalyzer = new Analyzer({ methodLevel: true });
|
|
288
|
-
const analysis = await methodAnalyzer.analyze(files);
|
|
289
|
-
|
|
290
|
-
const methods = [];
|
|
291
|
-
if (analysis.files) {
|
|
292
|
-
for (const file of analysis.files) {
|
|
293
|
-
if (file.methods && file.methods.length > 0) {
|
|
294
|
-
methods.push({
|
|
295
|
-
file: file.relativePath,
|
|
296
|
-
methods: file.methods
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
return {
|
|
303
|
-
content: [{ type: "text", text: JSON.stringify(methods, null, 2) }]
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
default:
|
|
307
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
308
|
-
}
|
|
309
|
-
} catch (error) {
|
|
490
|
+
// With a root, keep the diff to that directory even if the repository is larger
|
|
491
|
+
diff = gitClient.exec(['diff', branch, '--', ...(this.root ? ['.'] : [])]);
|
|
492
|
+
} catch (e) {
|
|
493
|
+
return {
|
|
494
|
+
content: [{ type: 'text', text: `Error executing git diff: ${e.message}` }],
|
|
495
|
+
isError: true,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
content: [{ type: 'text', text: diff || 'No changes detected.' }],
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
case 'search_code': {
|
|
504
|
+
const { path: projectPath, query, type = 'regex' } = args;
|
|
505
|
+
if (type === 'ranked') {
|
|
506
|
+
const result = searchProject(projectPath, query, {
|
|
507
|
+
limit: args.limit ?? DEFAULT_LIMIT,
|
|
508
|
+
maxTokens: args.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
509
|
+
redact: args.redact !== false,
|
|
510
|
+
});
|
|
511
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
512
|
+
}
|
|
513
|
+
let pattern = null;
|
|
514
|
+
if (type === 'regex') {
|
|
515
|
+
try {
|
|
516
|
+
pattern = new RegExp(query);
|
|
517
|
+
} catch (e) {
|
|
310
518
|
return {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
type: "text",
|
|
314
|
-
text: `Error: ${error.message}`,
|
|
315
|
-
},
|
|
316
|
-
],
|
|
317
|
-
isError: true,
|
|
519
|
+
content: [{ type: 'text', text: `Invalid regular expression: ${e.message}` }],
|
|
520
|
+
isError: true,
|
|
318
521
|
};
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const scanner = new Scanner(projectPath);
|
|
525
|
+
const files = scanner.scan();
|
|
526
|
+
const results = [];
|
|
527
|
+
|
|
528
|
+
for (const file of files) {
|
|
529
|
+
try {
|
|
530
|
+
const content = fs.readFileSync(file.path, 'utf-8');
|
|
531
|
+
const lines = content.split('\n');
|
|
532
|
+
|
|
533
|
+
lines.forEach((line, index) => {
|
|
534
|
+
const match = pattern ? pattern.test(line) : line.includes(query);
|
|
535
|
+
|
|
536
|
+
if (match) {
|
|
537
|
+
results.push({
|
|
538
|
+
file: file.relativePath,
|
|
539
|
+
line: index + 1,
|
|
540
|
+
content: line.trim(),
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
} catch (_e) {
|
|
545
|
+
// Skip read errors
|
|
546
|
+
}
|
|
319
547
|
}
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
548
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
549
|
+
return {
|
|
550
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
case 'list_methods': {
|
|
554
|
+
let projectPath = args.path;
|
|
555
|
+
let file = args.file;
|
|
556
|
+
// `path` may name a single file instead of the project root
|
|
557
|
+
if (!file && fs.statSync(projectPath, { throwIfNoEntry: false })?.isFile()) {
|
|
558
|
+
file = path.basename(projectPath);
|
|
559
|
+
projectPath = path.dirname(projectPath);
|
|
560
|
+
}
|
|
561
|
+
const scanner = new Scanner(projectPath);
|
|
562
|
+
// Optional `file`: one project-relative file instead of the whole project
|
|
563
|
+
const target = file ? path.normalize(file) : null;
|
|
564
|
+
const files = scanner
|
|
565
|
+
.scan()
|
|
566
|
+
.filter((file) => !target || path.normalize(file.relativePath) === target);
|
|
567
|
+
if (target && files.length === 0) {
|
|
568
|
+
return {
|
|
569
|
+
content: [{ type: 'text', text: `File not found in project: ${file}` }],
|
|
570
|
+
isError: true,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Use Analyzer with methodLevel: true
|
|
575
|
+
const methodAnalyzer = new Analyzer({ methodLevel: true });
|
|
576
|
+
const analysis = await methodAnalyzer.analyze(files);
|
|
577
|
+
|
|
578
|
+
const methods = [];
|
|
579
|
+
if (analysis.files) {
|
|
580
|
+
for (const file of analysis.files) {
|
|
581
|
+
if (file.methods && file.methods.length > 0) {
|
|
582
|
+
methods.push({
|
|
583
|
+
file: file.relativePath,
|
|
584
|
+
methods: file.methods,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
328
589
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
590
|
+
return {
|
|
591
|
+
content: [{ type: 'text', text: JSON.stringify(methods, null, 2) }],
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
case 'repo_map': {
|
|
595
|
+
const map = renderRepoMap(args.path, {
|
|
596
|
+
tokens: args.tokens,
|
|
597
|
+
focus: args.focus,
|
|
598
|
+
});
|
|
599
|
+
const notes = map.warnings.map((warning) => `Note: ${warning}`).join('\n');
|
|
600
|
+
return {
|
|
601
|
+
content: [
|
|
602
|
+
{
|
|
603
|
+
type: 'text',
|
|
604
|
+
text: [map.output.trimEnd(), notes].filter(Boolean).join('\n\n') || 'Empty map.',
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
case 'pr_context': {
|
|
610
|
+
const result = await buildPrContext(args.path, {
|
|
611
|
+
base: args.base,
|
|
612
|
+
head: args.head,
|
|
613
|
+
budget: args.budget == null ? undefined : Math.floor(args.budget),
|
|
614
|
+
style: args.style,
|
|
615
|
+
});
|
|
616
|
+
return { content: [{ type: 'text', text: result.output }] };
|
|
617
|
+
}
|
|
618
|
+
case 'get_symbol': {
|
|
619
|
+
const result = getSymbol(args.path, args.name, {
|
|
620
|
+
file: args.file,
|
|
621
|
+
redact: args.redact !== false,
|
|
622
|
+
});
|
|
623
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
624
|
+
}
|
|
625
|
+
case 'count_tokens': {
|
|
626
|
+
if (args.text === undefined && args.path === undefined) {
|
|
627
|
+
throw new Error('Give a project path or a text to count');
|
|
628
|
+
}
|
|
629
|
+
const { largestFiles, ...summary } = await countForModel(args.path, {
|
|
630
|
+
model: args.model,
|
|
631
|
+
text: args.text,
|
|
632
|
+
});
|
|
633
|
+
const top = Number.isInteger(args.top) && args.top >= 0 ? args.top : 10;
|
|
634
|
+
const result = { ...summary, largestFiles: largestFiles.slice(0, top) };
|
|
635
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
636
|
+
}
|
|
637
|
+
case 'explain_history': {
|
|
638
|
+
const whole = (value) => (typeof value === 'number' ? Math.floor(value) : value);
|
|
639
|
+
const result = await explainHistory(args.path, args.target, {
|
|
640
|
+
limit: whole(args.limit),
|
|
641
|
+
budget: whole(args.budget ?? 20000),
|
|
642
|
+
since: args.since,
|
|
643
|
+
patches: args.patches === true,
|
|
644
|
+
});
|
|
645
|
+
return { content: [{ type: 'text', text: result.text }] };
|
|
646
|
+
}
|
|
647
|
+
case 'pack_context': {
|
|
648
|
+
const result = await packContext(args.path, {
|
|
649
|
+
include: args.include,
|
|
650
|
+
exclude: args.exclude,
|
|
651
|
+
// Relative to the project; packContext rejects paths that leave it
|
|
652
|
+
files: args.files,
|
|
653
|
+
cwd: args.path,
|
|
654
|
+
style: args.style,
|
|
655
|
+
compress: args.compress,
|
|
656
|
+
lineNumbers: args.lineNumbers,
|
|
657
|
+
tree: args.tree,
|
|
658
|
+
instructions: args.instructions,
|
|
659
|
+
budget: args.budget == null ? TOOL_DEFAULT_BUDGET : Math.floor(args.budget),
|
|
660
|
+
targetModel: args.targetModel,
|
|
661
|
+
});
|
|
662
|
+
return {
|
|
663
|
+
content: [
|
|
664
|
+
{ type: 'text', text: result.text },
|
|
665
|
+
{ type: 'text', text: describePack(result) },
|
|
666
|
+
],
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
default:
|
|
670
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
333
671
|
}
|
|
334
|
-
|
|
335
|
-
|
|
672
|
+
} catch (error) {
|
|
673
|
+
return {
|
|
674
|
+
content: [
|
|
675
|
+
{
|
|
676
|
+
type: 'text',
|
|
677
|
+
text: `Error: ${error.message}`,
|
|
678
|
+
},
|
|
679
|
+
],
|
|
680
|
+
isError: true,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
setupErrorHandling() {
|
|
687
|
+
this.server.onerror = (error) => {
|
|
688
|
+
console.error('[MCP Error]', error);
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async start(transport) {
|
|
693
|
+
if (!transport) {
|
|
694
|
+
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
695
|
+
transport = new StdioServerTransport();
|
|
336
696
|
}
|
|
697
|
+
await this.server.connect(transport);
|
|
698
|
+
console.error('Ctxman MCP Server running');
|
|
699
|
+
}
|
|
337
700
|
}
|
|
338
701
|
|
|
339
702
|
export default MCPServer;
|