-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathcontext-handler.js
More file actions
173 lines (164 loc) · 6.05 KB
/
Copy pathcontext-handler.js
File metadata and controls
173 lines (164 loc) · 6.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* Pure routing logic for unreal_get_ue_context.
* Extracted from index.js so it can be unit-tested without the MCP server.
*
* @param {object} args - Tool call arguments
* @param {object} loaders - Context-loader functions (injected for testability)
* @returns {object} MCP response { content: [{type, text}], isError? }
*/
export function resolveUeContextRequest(args, {
listCategories,
listSections,
getSectionByHeading,
loadContextForCategory,
getSectionsForQuery,
getCategoryInfo,
}) {
const { category, query, section, mode, max_sections } = args || {};
const maxSections = Math.min(Math.max(1, Number(max_sections) || 3), 8);
// Global outline OR no arguments (no category)
if (!category && (mode === "outline" || (!query && !section))) {
const lines = listCategories().map((cat) => {
const headings = listSections(cat);
const info = getCategoryInfo(cat);
const sectionList = headings.length > 0
? headings.map((h) => ` - ${h}`).join("\n")
: ` (keywords: ${info.keywords.slice(0, 4).join(", ")})`;
return `**${cat}**\n${sectionList}`;
});
return {
content: [{
type: "text",
text: `# UE 5.7 Context — Available Sections\n\nUse \`query\` for targeted loading or \`category\`+\`section\` for a specific section.\n\n${lines.join("\n\n")}`,
}],
};
}
// Per-category outline
if (category && mode === "outline") {
const headings = listSections(category);
if (headings.length === 0) {
return {
content: [{ type: "text", text: `Category "${category}" has no sub-sections. Use mode="full" to load it entirely.` }],
};
}
return {
content: [{ type: "text", text: `# ${category} — Sections\n\n${headings.map((h) => `- ${h}`).join("\n")}` }],
};
}
// category + section: specific section
if (category && section) {
const body = getSectionByHeading(category, section);
if (!body) {
const available = listSections(category);
return {
content: [{
type: "text",
text: `Section "${section}" not found in "${category}". Available: ${available.join(", ") || "(none — use mode=full)"}`,
}],
isError: true,
};
}
return {
content: [{ type: "text", text: `# UE 5.7 — ${category} › ${section}\n\n${body}` }],
};
}
// category + mode=full: entire file
if (category && mode === "full") {
const content = loadContextForCategory(category);
if (!content) {
return {
content: [{ type: "text", text: `Unknown category: "${category}". Available: ${listCategories().join(", ")}` }],
isError: true,
};
}
return {
content: [{ type: "text", text: `# UE 5.7 Context: ${category}\n\n${content}` }],
};
}
// query → targeted sections
if (query) {
const result = getSectionsForQuery(query, { category, maxSections });
if (!result) {
return {
content: [{
type: "text",
text: `No context found for query: "${query}". Try mode="outline" to see available categories and sections.`,
}],
};
}
const parts = result.sections.map(
(s) => `## [${s.category}] ${s.heading}\n\n${s.body}`
);
const header = `# UE 5.7 Context — ${result.sections.length} section(s) matching "${query}"` +
(result.sections.length < result.totalScanned
? ` (showing ${result.sections.length}/${result.totalScanned} scored sections)`
: "");
return {
content: [{ type: "text", text: `${header}\n\n${parts.join("\n\n---\n\n")}` }],
};
}
// category alone (no query, no mode): per-category outline
if (category) {
const headings = listSections(category);
if (headings.length === 0) {
const content = loadContextForCategory(category);
if (!content) {
return {
content: [{ type: "text", text: `Unknown category: "${category}". Available: ${listCategories().join(", ")}` }],
isError: true,
};
}
return {
content: [{ type: "text", text: `# UE 5.7 Context: ${category}\n\n${content}` }],
};
}
return {
content: [{
type: "text",
text: `# ${category} — Sections\n\n${headings.map((h) => `- ${h}`).join("\n")}\n\nUse \`section\` param to load a specific section, or add \`query\` to target relevant sections.`,
}],
};
}
return {
content: [{ type: "text", text: "Provide at least one of: query, category, section. Use mode=outline to explore available sections." }],
isError: true,
};
}
/**
* Handler for unreal_get_project_context. Probes editor connectivity, then fetches the
* editor's /mcp/project_context endpoint. Dependencies are injected so the three branches
* (disconnected, HTTP error, success) can be unit-tested without a live editor.
*
* @param {object} deps
* @param {() => Promise<{connected: boolean}>} deps.checkConnection - editor reachability probe
* @param {typeof fetch} deps.fetchImpl - fetch implementation (injected for testability)
* @param {string} deps.url - base Unreal MCP URL (no trailing slash)
* @param {number} deps.timeoutMs - per-request timeout in milliseconds
* @returns {Promise<object>} MCP response { content: [{type, text}], isError? }
*/
export async function resolveProjectContextRequest({ checkConnection, fetchImpl, url, timeoutMs }) {
const status = await checkConnection();
if (!status.connected) {
return {
content: [{ type: "text", text: "Unreal Editor not connected. Start the editor with the plugin enabled." }],
isError: true,
};
}
try {
const response = await fetchImpl(`${url}/mcp/project_context`, {
signal: AbortSignal.timeout(timeoutMs),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return {
content: [{ type: "text", text: data.context || "No project context available." }],
};
} catch (err) {
return {
content: [{ type: "text", text: `Failed to fetch project context: ${err.message}` }],
isError: true,
};
}
}