@gtkx/mcp 1.0.0-rc.3 → 1.0.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/README.md +8 -9
- package/dist/internal.d.ts +2 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -2
- package/dist/internal.js.map +1 -1
- package/dist/protocol/errors.d.ts +3 -1
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js +9 -1
- package/dist/protocol/errors.js.map +1 -1
- package/dist/protocol/schemas.d.ts +18 -4
- package/dist/protocol/schemas.d.ts.map +1 -1
- package/dist/protocol/schemas.js +10 -3
- package/dist/protocol/schemas.js.map +1 -1
- package/dist/reference.d.ts +17 -3
- package/dist/reference.d.ts.map +1 -1
- package/dist/reference.js +123 -64
- package/dist/reference.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +120 -57
- package/dist/server.js.map +1 -1
- package/dist/socket-server.d.ts +10 -1
- package/dist/socket-server.d.ts.map +1 -1
- package/dist/socket-server.js +234 -33
- package/dist/socket-server.js.map +1 -1
- package/dist/transport.d.ts +1 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js.map +1 -1
- package/package.json +4 -4
- package/src/internal.ts +4 -0
- package/src/protocol/errors.ts +14 -0
- package/src/protocol/schemas.ts +32 -6
- package/src/reference.ts +206 -83
- package/src/server.ts +162 -72
- package/src/socket-server.ts +309 -40
- package/src/transport.ts +0 -1
package/src/reference.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type ApiReference, type ApiSymbol, loadApiReference, resolveGirPath, resolveLibraries } from "@gtkx/codegen";
|
|
2
2
|
import { loadConfig } from "@gtkx/config";
|
|
3
3
|
import { type McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
-
import { ErrorCode, McpError, type ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
import { statSync } from "node:fs";
|
|
6
|
-
import { resolve } from "node:path";
|
|
4
|
+
import { type CallToolResult, ErrorCode, McpError, type ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { existsSync, statSync } from "node:fs";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { defineTool, textContent, textError, type Tool, type ToolArgs } from "./tool.js";
|
|
9
9
|
|
|
@@ -12,8 +12,26 @@ type ReferenceApi = Pick<
|
|
|
12
12
|
"lookup" | "namespaceOverview" | "namespaces" | "overview" | "search" | "symbolNames"
|
|
13
13
|
>;
|
|
14
14
|
|
|
15
|
+
type ProjectSource = "argument" | "workingDirectory" | "app";
|
|
16
|
+
|
|
17
|
+
type ResolvedProject = {
|
|
18
|
+
root: string;
|
|
19
|
+
source: ProjectSource;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type ScopedReference = ResolvedProject & {
|
|
23
|
+
reference: ReferenceApi;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ReferenceProviderOptions = {
|
|
27
|
+
getAppRoot: () => string | undefined;
|
|
28
|
+
getWorkingDirectory?: () => string;
|
|
29
|
+
};
|
|
30
|
+
|
|
15
31
|
type ReferenceProvider = {
|
|
16
|
-
get(): Promise<
|
|
32
|
+
get(projectRoot?: string): Promise<ScopedReference>;
|
|
33
|
+
load(project: ResolvedProject): Promise<ScopedReference>;
|
|
34
|
+
resolve(projectRoot?: string): ResolvedProject;
|
|
17
35
|
};
|
|
18
36
|
|
|
19
37
|
type WatchedFile = {
|
|
@@ -24,6 +42,7 @@ type WatchedFile = {
|
|
|
24
42
|
|
|
25
43
|
type LoadedReference = {
|
|
26
44
|
reference: ApiReference;
|
|
45
|
+
root: string;
|
|
27
46
|
watched: WatchedFile[];
|
|
28
47
|
};
|
|
29
48
|
|
|
@@ -39,6 +58,13 @@ type ResourceServer = Pick<McpServer, "registerResource">;
|
|
|
39
58
|
|
|
40
59
|
const FRESHNESS_INTERVAL_MS = 2000;
|
|
41
60
|
const FAILURE_RETRY_MS = 5000;
|
|
61
|
+
const CONFIG_EXTENSIONS = ["ts", "mts", "cts", "js", "mjs", "cjs", "json"];
|
|
62
|
+
|
|
63
|
+
const PROJECT_SOURCE_LABELS: Record<ProjectSource, string> = {
|
|
64
|
+
argument: "requested with `projectRoot`",
|
|
65
|
+
workingDirectory: "found from the working directory",
|
|
66
|
+
app: "taken from a connected app; pass `projectRoot` to document another project",
|
|
67
|
+
};
|
|
42
68
|
|
|
43
69
|
const SYMBOL_KIND = z.enum([
|
|
44
70
|
"element",
|
|
@@ -56,7 +82,17 @@ const SYMBOL_DESCRIPTION =
|
|
|
56
82
|
"Qualified symbol name (`Gtk.Button`, `Gtk.Orientation`, `GLib.idleAdd`), JSX element name (`GtkButton`), " +
|
|
57
83
|
"or bare symbol name when unambiguous (`Button`).";
|
|
58
84
|
|
|
85
|
+
const PROJECT_ROOT_DESCRIPTION =
|
|
86
|
+
"Directory of the GTKX project whose bindings to document, absolute or relative to the working directory. " +
|
|
87
|
+
"Any directory inside the project works; its `gtkx.config.ts` decides the documented libraries. Omit to use " +
|
|
88
|
+
"the project containing the working directory, falling back to a connected app's project.";
|
|
89
|
+
|
|
90
|
+
const projectRootShape = {
|
|
91
|
+
projectRoot: z.string().optional().describe(PROJECT_ROOT_DESCRIPTION),
|
|
92
|
+
};
|
|
93
|
+
|
|
59
94
|
const listApiShape = {
|
|
95
|
+
...projectRootShape,
|
|
60
96
|
namespace: z
|
|
61
97
|
.string()
|
|
62
98
|
.optional()
|
|
@@ -64,6 +100,7 @@ const listApiShape = {
|
|
|
64
100
|
};
|
|
65
101
|
|
|
66
102
|
const searchApiShape = {
|
|
103
|
+
...projectRootShape,
|
|
67
104
|
query: z.string().describe("Case-insensitive substring of a symbol name, e.g. `headerbar` or `orientation`."),
|
|
68
105
|
namespace: z.string().optional().describe("Restrict matches to one namespace (e.g. `Gtk`)."),
|
|
69
106
|
kind: SYMBOL_KIND.optional().describe("Restrict matches to one symbol kind."),
|
|
@@ -71,6 +108,7 @@ const searchApiShape = {
|
|
|
71
108
|
};
|
|
72
109
|
|
|
73
110
|
const apiDocsShape = {
|
|
111
|
+
...projectRootShape,
|
|
74
112
|
symbol: z.string().describe(SYMBOL_DESCRIPTION),
|
|
75
113
|
kind: SYMBOL_KIND.optional().describe("Disambiguate when several kinds share the symbol name."),
|
|
76
114
|
};
|
|
@@ -92,13 +130,55 @@ const isFresh = (loaded: LoadedReference): boolean =>
|
|
|
92
130
|
return current.mtimeMs === file.mtimeMs && current.size === file.size;
|
|
93
131
|
});
|
|
94
132
|
|
|
95
|
-
const
|
|
96
|
-
|
|
133
|
+
const hasConfigFile = (directory: string): boolean =>
|
|
134
|
+
CONFIG_EXTENSIONS.some((extension) => existsSync(join(directory, `gtkx.config.${extension}`)));
|
|
135
|
+
|
|
136
|
+
const findProjectRoot = (start: string): string | undefined => {
|
|
137
|
+
const current = resolve(start);
|
|
138
|
+
const parent = dirname(current);
|
|
139
|
+
|
|
140
|
+
if (hasConfigFile(current)) {
|
|
141
|
+
return current;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return parent === current ? undefined : findProjectRoot(parent);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const projectAt = (candidate: string, source: ProjectSource): ResolvedProject => ({
|
|
148
|
+
root: findProjectRoot(candidate) ?? resolve(candidate),
|
|
149
|
+
source,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const resolveProject = (
|
|
153
|
+
workingDirectory: string,
|
|
154
|
+
getAppRoot: () => string | undefined,
|
|
155
|
+
projectRoot: string | undefined,
|
|
156
|
+
): ResolvedProject => {
|
|
157
|
+
if (projectRoot !== undefined) {
|
|
158
|
+
return projectAt(projectRoot, "argument");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const discovered = findProjectRoot(workingDirectory);
|
|
162
|
+
|
|
163
|
+
if (discovered !== undefined) {
|
|
164
|
+
return { root: discovered, source: "workingDirectory" };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const appRoot = getAppRoot();
|
|
168
|
+
|
|
169
|
+
return appRoot === undefined
|
|
170
|
+
? { root: resolve(workingDirectory), source: "workingDirectory" }
|
|
171
|
+
: projectAt(appRoot, "app");
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const loadReference = async (requestedRoot: string): Promise<LoadedReference> => {
|
|
175
|
+
const { config, configFile, root } = await loadConfig(requestedRoot);
|
|
97
176
|
|
|
98
177
|
if (config.codegen === false) {
|
|
99
178
|
throw new Error(
|
|
100
179
|
`codegen is disabled for the project at ${root}, so there are no generated bindings to document. ` +
|
|
101
|
-
"Remove `codegen: false` from gtkx.config.ts to use the API reference
|
|
180
|
+
"Remove `codegen: false` from gtkx.config.ts to use the API reference, or point the `projectRoot` " +
|
|
181
|
+
"argument at another project.",
|
|
102
182
|
);
|
|
103
183
|
}
|
|
104
184
|
|
|
@@ -116,7 +196,7 @@ const loadReference = async (root: string): Promise<LoadedReference> => {
|
|
|
116
196
|
const reference = loadApiReference({ libraries, girPath });
|
|
117
197
|
const watched = [watchFile(resolve(root, configFile)), ...reference.girFiles.map((file) => watchFile(file))];
|
|
118
198
|
|
|
119
|
-
return { reference, watched };
|
|
199
|
+
return { reference, root, watched };
|
|
120
200
|
};
|
|
121
201
|
|
|
122
202
|
const markFailed = async (entry: CacheEntry): Promise<void> => {
|
|
@@ -150,31 +230,67 @@ const revalidate = (cache: ReferenceCache, root: string, entry: CacheEntry): Cac
|
|
|
150
230
|
return current === undefined || current === entry ? startLoad(cache, root) : current;
|
|
151
231
|
};
|
|
152
232
|
|
|
153
|
-
const currentReference = async (cache: ReferenceCache, root: string): Promise<
|
|
233
|
+
const currentReference = async (cache: ReferenceCache, root: string): Promise<LoadedReference> => {
|
|
154
234
|
const entry = resolveEntry(cache, root);
|
|
155
235
|
const loaded = await entry.pending;
|
|
156
236
|
|
|
157
237
|
if (Date.now() - entry.verifiedAt < FRESHNESS_INTERVAL_MS) {
|
|
158
|
-
return loaded
|
|
238
|
+
return loaded;
|
|
159
239
|
}
|
|
160
240
|
|
|
161
241
|
if (isFresh(loaded)) {
|
|
162
242
|
entry.verifiedAt = Date.now();
|
|
163
243
|
|
|
164
|
-
return loaded
|
|
244
|
+
return loaded;
|
|
165
245
|
}
|
|
166
246
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return revalidated.reference;
|
|
247
|
+
return revalidate(cache, root, entry).pending;
|
|
170
248
|
};
|
|
171
249
|
|
|
172
|
-
const
|
|
250
|
+
const defaultWorkingDirectory = (): string => process.cwd();
|
|
251
|
+
|
|
252
|
+
const createReferenceProvider = (options: ReferenceProviderOptions): ReferenceProvider => {
|
|
173
253
|
const cache: ReferenceCache = new Map();
|
|
254
|
+
const getWorkingDirectory = options.getWorkingDirectory ?? defaultWorkingDirectory;
|
|
174
255
|
|
|
175
|
-
|
|
176
|
-
|
|
256
|
+
const resolve = (projectRoot?: string): ResolvedProject =>
|
|
257
|
+
resolveProject(getWorkingDirectory(), options.getAppRoot, projectRoot);
|
|
258
|
+
|
|
259
|
+
const load = async (project: ResolvedProject): Promise<ScopedReference> => {
|
|
260
|
+
const loaded = await currentReference(cache, project.root);
|
|
261
|
+
|
|
262
|
+
return { reference: loaded.reference, root: loaded.root, source: project.source };
|
|
177
263
|
};
|
|
264
|
+
|
|
265
|
+
const get = (projectRoot?: string): Promise<ScopedReference> => load(resolve(projectRoot));
|
|
266
|
+
|
|
267
|
+
return { get, load, resolve };
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const projectNote = (project: ResolvedProject): string =>
|
|
271
|
+
`Project: ${project.root} (${PROJECT_SOURCE_LABELS[project.source]})`;
|
|
272
|
+
|
|
273
|
+
const withProjectNote = (result: CallToolResult, note: string): CallToolResult => ({
|
|
274
|
+
...result,
|
|
275
|
+
content: [...result.content, { type: "text", text: note }],
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const scopedResult = async (
|
|
279
|
+
provider: ReferenceProvider,
|
|
280
|
+
projectRoot: string | undefined,
|
|
281
|
+
render: (reference: ReferenceApi) => CallToolResult,
|
|
282
|
+
): Promise<CallToolResult> => {
|
|
283
|
+
const resolved = provider.resolve(projectRoot);
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const scoped = await provider.load(resolved);
|
|
287
|
+
|
|
288
|
+
return withProjectNote(render(scoped.reference), projectNote(scoped));
|
|
289
|
+
} catch (error) {
|
|
290
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
291
|
+
|
|
292
|
+
return withProjectNote(textError(message), projectNote(resolved));
|
|
293
|
+
}
|
|
178
294
|
};
|
|
179
295
|
|
|
180
296
|
const formatCandidates = (candidates: ApiSymbol[]): string =>
|
|
@@ -198,6 +314,52 @@ const buildSearchOptions = (args: ToolArgs<typeof searchApiShape>): SearchOption
|
|
|
198
314
|
return options;
|
|
199
315
|
};
|
|
200
316
|
|
|
317
|
+
const listApiResult = (reference: ReferenceApi, namespace: string | undefined): CallToolResult => {
|
|
318
|
+
if (namespace === undefined) {
|
|
319
|
+
return textContent(reference.overview());
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const overview = reference.namespaceOverview(namespace);
|
|
323
|
+
|
|
324
|
+
if (overview === undefined) {
|
|
325
|
+
const names = reference
|
|
326
|
+
.namespaces()
|
|
327
|
+
.map((summary) => summary.name)
|
|
328
|
+
.join(", ");
|
|
329
|
+
|
|
330
|
+
return textError(`Unknown namespace "${namespace}". Available namespaces: ${names}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return textContent(overview);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const searchApiResult = (reference: ReferenceApi, args: ToolArgs<typeof searchApiShape>): CallToolResult => {
|
|
337
|
+
const results = reference.search(buildSearchOptions(args));
|
|
338
|
+
|
|
339
|
+
if (results.length === 0) {
|
|
340
|
+
return textContent(`No symbols matched "${args.query}". Try a shorter substring or \`gtkx_list_api\`.`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return textContent(JSON.stringify(results, null, 2));
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const apiDocsResult = (reference: ReferenceApi, args: ToolArgs<typeof apiDocsShape>): CallToolResult => {
|
|
347
|
+
const result = reference.lookup(args.symbol, args.kind);
|
|
348
|
+
|
|
349
|
+
if (result.outcome === "notFound") {
|
|
350
|
+
return textError(`No symbol named "${args.symbol}". Use \`gtkx_search_api\` to find the right name.`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (result.outcome === "ambiguous") {
|
|
354
|
+
return textError(
|
|
355
|
+
`"${args.symbol}" matches several symbols. Pass a qualified name or a kind:\n` +
|
|
356
|
+
formatCandidates(result.candidates),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return textContent(result.markdown);
|
|
361
|
+
};
|
|
362
|
+
|
|
201
363
|
const listApiTool = (provider: ReferenceProvider): Tool =>
|
|
202
364
|
defineTool({
|
|
203
365
|
name: "gtkx_list_api",
|
|
@@ -207,26 +369,8 @@ const listApiTool = (provider: ReferenceProvider): Tool =>
|
|
|
207
369
|
"List the project's generated GTK4 bindings API (`@gtkx/gi` and `@gtkx/jsx`). Without a namespace, " +
|
|
208
370
|
"returns every namespace with symbol counts; with a namespace, lists all of its symbols grouped by kind.",
|
|
209
371
|
inputSchema: listApiShape,
|
|
210
|
-
handler:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (namespace === undefined) {
|
|
214
|
-
return textContent(reference.overview());
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const overview = reference.namespaceOverview(namespace);
|
|
218
|
-
|
|
219
|
-
if (overview === undefined) {
|
|
220
|
-
const names = reference
|
|
221
|
-
.namespaces()
|
|
222
|
-
.map((summary) => summary.name)
|
|
223
|
-
.join(", ");
|
|
224
|
-
|
|
225
|
-
return textError(`Unknown namespace "${namespace}". Available namespaces: ${names}`);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return textContent(overview);
|
|
229
|
-
},
|
|
372
|
+
handler: ({ namespace, projectRoot }) =>
|
|
373
|
+
scopedResult(provider, projectRoot, (reference) => listApiResult(reference, namespace)),
|
|
230
374
|
});
|
|
231
375
|
|
|
232
376
|
const searchApiTool = (provider: ReferenceProvider): Tool =>
|
|
@@ -238,16 +382,7 @@ const searchApiTool = (provider: ReferenceProvider): Tool =>
|
|
|
238
382
|
"Search the project's generated GTK4 bindings API by symbol name. Returns matching symbols with " +
|
|
239
383
|
"their namespace, kind, and a one-line summary; fetch full pages with `gtkx_get_api_docs`.",
|
|
240
384
|
inputSchema: searchApiShape,
|
|
241
|
-
handler:
|
|
242
|
-
const reference = await provider.get();
|
|
243
|
-
const results = reference.search(buildSearchOptions(args));
|
|
244
|
-
|
|
245
|
-
if (results.length === 0) {
|
|
246
|
-
return textContent(`No symbols matched "${args.query}". Try a shorter substring or \`gtkx_list_api\`.`);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return textContent(JSON.stringify(results, null, 2));
|
|
250
|
-
},
|
|
385
|
+
handler: (args) => scopedResult(provider, args.projectRoot, (reference) => searchApiResult(reference, args)),
|
|
251
386
|
});
|
|
252
387
|
|
|
253
388
|
const getApiDocsTool = (provider: ReferenceProvider): Tool =>
|
|
@@ -260,23 +395,7 @@ const getApiDocsTool = (provider: ReferenceProvider): Tool =>
|
|
|
260
395
|
"(props, signals, methods) or `@gtkx/gi` classes, interfaces, records, enums, callbacks, aliases, " +
|
|
261
396
|
"functions, and constants.",
|
|
262
397
|
inputSchema: apiDocsShape,
|
|
263
|
-
handler:
|
|
264
|
-
const reference = await provider.get();
|
|
265
|
-
const result = reference.lookup(symbol, kind);
|
|
266
|
-
|
|
267
|
-
if (result.outcome === "notFound") {
|
|
268
|
-
return textError(`No symbol named "${symbol}". Use \`gtkx_search_api\` to find the right name.`);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (result.outcome === "ambiguous") {
|
|
272
|
-
return textError(
|
|
273
|
-
`"${symbol}" matches several symbols. Pass a qualified name or a kind:\n` +
|
|
274
|
-
formatCandidates(result.candidates),
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return textContent(result.markdown);
|
|
279
|
-
},
|
|
398
|
+
handler: (args) => scopedResult(provider, args.projectRoot, (reference) => apiDocsResult(reference, args)),
|
|
280
399
|
});
|
|
281
400
|
|
|
282
401
|
const buildReferenceTools = (provider: ReferenceProvider): Tool[] => [
|
|
@@ -300,28 +419,31 @@ const withLoadFallback = async <T>(load: () => Promise<T>, fallback: T): Promise
|
|
|
300
419
|
}
|
|
301
420
|
};
|
|
302
421
|
|
|
422
|
+
const namesStartingWith = (names: string[], value: string): string[] =>
|
|
423
|
+
names.filter((name) => name.toLowerCase().startsWith(value.toLowerCase()));
|
|
424
|
+
|
|
425
|
+
const completeNames = (
|
|
426
|
+
provider: ReferenceProvider,
|
|
427
|
+
value: string,
|
|
428
|
+
collect: (reference: ReferenceApi) => string[],
|
|
429
|
+
): Promise<string[]> =>
|
|
430
|
+
withLoadFallback(async () => {
|
|
431
|
+
const { reference } = await provider.get();
|
|
432
|
+
|
|
433
|
+
return namesStartingWith(collect(reference), value);
|
|
434
|
+
}, []);
|
|
435
|
+
|
|
303
436
|
const namespaceCompleter =
|
|
304
437
|
(provider: ReferenceProvider) =>
|
|
305
438
|
(value: string): Promise<string[]> =>
|
|
306
|
-
|
|
307
|
-
const reference = await provider.get();
|
|
308
|
-
|
|
309
|
-
return reference
|
|
310
|
-
.namespaces()
|
|
311
|
-
.map((summary) => summary.name)
|
|
312
|
-
.filter((name) => name.toLowerCase().startsWith(value.toLowerCase()));
|
|
313
|
-
}, []);
|
|
439
|
+
completeNames(provider, value, (reference) => reference.namespaces().map((summary) => summary.name));
|
|
314
440
|
|
|
315
|
-
const completeSymbol =
|
|
441
|
+
const completeSymbol = (provider: ReferenceProvider, namespace: string, value: string): Promise<string[]> => {
|
|
316
442
|
if (namespace.length === 0) {
|
|
317
|
-
return [];
|
|
443
|
+
return Promise.resolve([]);
|
|
318
444
|
}
|
|
319
445
|
|
|
320
|
-
return
|
|
321
|
-
const reference = await provider.get();
|
|
322
|
-
|
|
323
|
-
return reference.symbolNames(namespace).filter((name) => name.toLowerCase().startsWith(value.toLowerCase()));
|
|
324
|
-
}, []);
|
|
446
|
+
return completeNames(provider, value, (reference) => reference.symbolNames(namespace));
|
|
325
447
|
};
|
|
326
448
|
|
|
327
449
|
const resourceNotFound = (message: string): McpError => new McpError(ErrorCode.InvalidParams, message);
|
|
@@ -332,7 +454,7 @@ const symbolPage = async (
|
|
|
332
454
|
namespace: string,
|
|
333
455
|
symbol: string,
|
|
334
456
|
): Promise<ReadResourceResult> => {
|
|
335
|
-
const reference = await provider.get();
|
|
457
|
+
const { reference } = await provider.get();
|
|
336
458
|
const result = reference.lookup(`${namespace}.${symbol}`);
|
|
337
459
|
|
|
338
460
|
if (result.outcome === "page") {
|
|
@@ -358,7 +480,7 @@ const registerIndexResource = (server: ResourceServer, provider: ReferenceProvid
|
|
|
358
480
|
mimeType: "text/markdown",
|
|
359
481
|
},
|
|
360
482
|
async (uri) => {
|
|
361
|
-
const reference = await provider.get();
|
|
483
|
+
const { reference } = await provider.get();
|
|
362
484
|
|
|
363
485
|
return markdownResource(uri, reference.overview());
|
|
364
486
|
},
|
|
@@ -372,7 +494,7 @@ const registerNamespaceResource = (server: ResourceServer, provider: ReferencePr
|
|
|
372
494
|
list: () =>
|
|
373
495
|
withLoadFallback(
|
|
374
496
|
async () => {
|
|
375
|
-
const reference = await provider.get();
|
|
497
|
+
const { reference } = await provider.get();
|
|
376
498
|
|
|
377
499
|
return {
|
|
378
500
|
resources: reference.namespaces().map((summary) => ({
|
|
@@ -395,7 +517,7 @@ const registerNamespaceResource = (server: ResourceServer, provider: ReferencePr
|
|
|
395
517
|
},
|
|
396
518
|
async (uri, variables) => {
|
|
397
519
|
const namespace = variableValue(variables.namespace);
|
|
398
|
-
const reference = await provider.get();
|
|
520
|
+
const { reference } = await provider.get();
|
|
399
521
|
const overview = reference.namespaceOverview(namespace);
|
|
400
522
|
|
|
401
523
|
if (overview === undefined) {
|
|
@@ -442,4 +564,5 @@ export {
|
|
|
442
564
|
registerReferenceResources,
|
|
443
565
|
type ReferenceApi,
|
|
444
566
|
type ReferenceProvider,
|
|
567
|
+
type ResolvedProject,
|
|
445
568
|
};
|