@loomweaver/mcp 0.8.3 → 0.9.0-preview.10
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/dist/main.mjs +320 -43
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -2984,7 +2984,7 @@ var require_compile = __commonJS({
|
|
|
2984
2984
|
const schOrFunc = root.refs[ref];
|
|
2985
2985
|
if (schOrFunc)
|
|
2986
2986
|
return schOrFunc;
|
|
2987
|
-
let _sch =
|
|
2987
|
+
let _sch = resolve2.call(this, root, ref);
|
|
2988
2988
|
if (_sch === void 0) {
|
|
2989
2989
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
2990
2990
|
const { schemaId } = this.opts;
|
|
@@ -3011,7 +3011,7 @@ var require_compile = __commonJS({
|
|
|
3011
3011
|
function sameSchemaEnv(s1, s2) {
|
|
3012
3012
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3013
3013
|
}
|
|
3014
|
-
function
|
|
3014
|
+
function resolve2(root, ref) {
|
|
3015
3015
|
let sch;
|
|
3016
3016
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3017
3017
|
ref = sch;
|
|
@@ -3642,7 +3642,7 @@ var require_fast_uri = __commonJS({
|
|
|
3642
3642
|
}
|
|
3643
3643
|
return uri;
|
|
3644
3644
|
}
|
|
3645
|
-
function
|
|
3645
|
+
function resolve2(baseURI, relativeURI, options) {
|
|
3646
3646
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3647
3647
|
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3648
3648
|
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
@@ -3926,7 +3926,7 @@ var require_fast_uri = __commonJS({
|
|
|
3926
3926
|
var fastUri = {
|
|
3927
3927
|
SCHEMES,
|
|
3928
3928
|
normalize,
|
|
3929
|
-
resolve,
|
|
3929
|
+
resolve: resolve2,
|
|
3930
3930
|
resolveComponent,
|
|
3931
3931
|
equal,
|
|
3932
3932
|
serialize,
|
|
@@ -23056,12 +23056,12 @@ var StdioServerTransport = class {
|
|
|
23056
23056
|
this.onclose?.();
|
|
23057
23057
|
}
|
|
23058
23058
|
send(message) {
|
|
23059
|
-
return new Promise((
|
|
23059
|
+
return new Promise((resolve2) => {
|
|
23060
23060
|
const json2 = serializeMessage(message);
|
|
23061
23061
|
if (this._stdout.write(json2)) {
|
|
23062
|
-
|
|
23062
|
+
resolve2();
|
|
23063
23063
|
} else {
|
|
23064
|
-
this._stdout.once("drain",
|
|
23064
|
+
this._stdout.once("drain", resolve2);
|
|
23065
23065
|
}
|
|
23066
23066
|
});
|
|
23067
23067
|
}
|
|
@@ -28980,7 +28980,7 @@ var Protocol = class {
|
|
|
28980
28980
|
return;
|
|
28981
28981
|
}
|
|
28982
28982
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
28983
|
-
await new Promise((
|
|
28983
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
28984
28984
|
options?.signal?.throwIfAborted();
|
|
28985
28985
|
}
|
|
28986
28986
|
} catch (error51) {
|
|
@@ -28997,7 +28997,7 @@ var Protocol = class {
|
|
|
28997
28997
|
*/
|
|
28998
28998
|
request(request, resultSchema, options) {
|
|
28999
28999
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
29000
|
-
return new Promise((
|
|
29000
|
+
return new Promise((resolve2, reject) => {
|
|
29001
29001
|
const earlyReject = (error51) => {
|
|
29002
29002
|
reject(error51);
|
|
29003
29003
|
};
|
|
@@ -29075,7 +29075,7 @@ var Protocol = class {
|
|
|
29075
29075
|
if (!parseResult.success) {
|
|
29076
29076
|
reject(parseResult.error);
|
|
29077
29077
|
} else {
|
|
29078
|
-
|
|
29078
|
+
resolve2(parseResult.data);
|
|
29079
29079
|
}
|
|
29080
29080
|
} catch (error51) {
|
|
29081
29081
|
reject(error51);
|
|
@@ -29336,12 +29336,12 @@ var Protocol = class {
|
|
|
29336
29336
|
}
|
|
29337
29337
|
} catch {
|
|
29338
29338
|
}
|
|
29339
|
-
return new Promise((
|
|
29339
|
+
return new Promise((resolve2, reject) => {
|
|
29340
29340
|
if (signal.aborted) {
|
|
29341
29341
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
29342
29342
|
return;
|
|
29343
29343
|
}
|
|
29344
|
-
const timeoutId = setTimeout(
|
|
29344
|
+
const timeoutId = setTimeout(resolve2, interval);
|
|
29345
29345
|
signal.addEventListener("abort", () => {
|
|
29346
29346
|
clearTimeout(timeoutId);
|
|
29347
29347
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -30432,7 +30432,7 @@ var McpServer = class {
|
|
|
30432
30432
|
let task = createTaskResult.task;
|
|
30433
30433
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
30434
30434
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
30435
|
-
await new Promise((
|
|
30435
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
30436
30436
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
30437
30437
|
if (!updatedTask) {
|
|
30438
30438
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -31175,6 +31175,12 @@ export class ${w.className}AgentPanel {
|
|
|
31175
31175
|
private runs = 0;
|
|
31176
31176
|
|
|
31177
31177
|
constructor() {
|
|
31178
|
+
this.refreshOffered();
|
|
31179
|
+
}
|
|
31180
|
+
|
|
31181
|
+
// The list is read again whenever the panel gains focus, because the weaver's own translations
|
|
31182
|
+
// and other plugins' commands may arrive after this component was built.
|
|
31183
|
+
protected refreshOffered(): void {
|
|
31178
31184
|
this.offered.set(${w.propertyName}Agent()?.list() ?? []);
|
|
31179
31185
|
}
|
|
31180
31186
|
|
|
@@ -31265,7 +31271,7 @@ export class ${w.className}AgentPanel {
|
|
|
31265
31271
|
`;
|
|
31266
31272
|
}
|
|
31267
31273
|
function panelTemplateFile(w) {
|
|
31268
|
-
return `<div class="flex h-full flex-col gap-3">
|
|
31274
|
+
return `<div class="flex h-full flex-col gap-3" (focusin)="refreshOffered()">
|
|
31269
31275
|
<p
|
|
31270
31276
|
class="shrink-0 rounded-md border border-border bg-surface-raised px-3 py-2 text-xs text-content-muted"
|
|
31271
31277
|
>
|
|
@@ -31381,10 +31387,10 @@ export async function* askAgent(
|
|
|
31381
31387
|
toolCallName: picked.name,
|
|
31382
31388
|
});
|
|
31383
31389
|
// Arguments arrive in pieces, exactly as they do from a real model. The adapter assembles them;
|
|
31384
|
-
// nothing downstream ever sees a half-written call.
|
|
31385
|
-
//
|
|
31386
|
-
//
|
|
31387
|
-
for (const piece of chunks(JSON.stringify(
|
|
31390
|
+
// nothing downstream ever sees a half-written call. The values come from the JSON Schema the
|
|
31391
|
+
// workbench described in picked.parameters: the first declared choice of the first argument that
|
|
31392
|
+
// has choices, and nothing for the rest, so the command applies its own defaults there.
|
|
31393
|
+
for (const piece of chunks(JSON.stringify(sampleArguments(picked)), 4)) {
|
|
31388
31394
|
yield event(EventType.TOOL_CALL_ARGS, { toolCallId, delta: piece });
|
|
31389
31395
|
await pause();
|
|
31390
31396
|
}
|
|
@@ -31409,6 +31415,18 @@ async function* speak(
|
|
|
31409
31415
|
yield event(EventType.TEXT_MESSAGE_END, { messageId });
|
|
31410
31416
|
}
|
|
31411
31417
|
|
|
31418
|
+
function sampleArguments(tool: Tool): Record<string, unknown> {
|
|
31419
|
+
const schema = tool.parameters as
|
|
31420
|
+
| { properties?: Record<string, { enum?: readonly unknown[] }> }
|
|
31421
|
+
| undefined;
|
|
31422
|
+
for (const [name, property] of Object.entries(schema?.properties ?? {})) {
|
|
31423
|
+
if (property.enum && property.enum.length > 0) {
|
|
31424
|
+
return { [name]: property.enum[0] };
|
|
31425
|
+
}
|
|
31426
|
+
}
|
|
31427
|
+
return {};
|
|
31428
|
+
}
|
|
31429
|
+
|
|
31412
31430
|
function chunks(text: string, size: number): readonly string[] {
|
|
31413
31431
|
const pieces: string[] = [];
|
|
31414
31432
|
for (let at = 0; at < text.length; at += size) {
|
|
@@ -31428,7 +31446,7 @@ function pause(): Promise<void> {
|
|
|
31428
31446
|
}
|
|
31429
31447
|
|
|
31430
31448
|
// ../devkit/src/recipes/angular-weaver/agent-files.ts
|
|
31431
|
-
var AG_UI_ADAPTER_VERSION = "0.
|
|
31449
|
+
var AG_UI_ADAPTER_VERSION = "0.9.0-preview.10";
|
|
31432
31450
|
var AG_UI_PROTOCOL_VERSION = "0.0.x";
|
|
31433
31451
|
function connectionFile(w) {
|
|
31434
31452
|
return `import { signal } from '@angular/core';
|
|
@@ -31494,7 +31512,7 @@ function contextThat(confirms: boolean, ran: Asked[]): PluginContext {
|
|
|
31494
31512
|
],
|
|
31495
31513
|
invokeCommand: (id: string, args?: CommandArguments) => {
|
|
31496
31514
|
ran.push({ id, args });
|
|
31497
|
-
return Promise.resolve({ outcome: 'answered', value: '
|
|
31515
|
+
return Promise.resolve({ outcome: 'answered', value: { tone: args?.['tone'] ?? 'info' } });
|
|
31498
31516
|
},
|
|
31499
31517
|
ui: { confirm: () => Promise.resolve(confirms) },
|
|
31500
31518
|
} as unknown as PluginContext;
|
|
@@ -31523,17 +31541,17 @@ describe('${w.propertyName}Connection', () => {
|
|
|
31523
31541
|
),
|
|
31524
31542
|
).toBeNull();
|
|
31525
31543
|
await tools.receive(
|
|
31526
|
-
event(EventType.TOOL_CALL_ARGS, { toolCallId: 'c1', delta: '{"
|
|
31544
|
+
event(EventType.TOOL_CALL_ARGS, { toolCallId: 'c1', delta: '{"tone"' }),
|
|
31527
31545
|
);
|
|
31528
31546
|
await tools.receive(
|
|
31529
|
-
event(EventType.TOOL_CALL_ARGS, { toolCallId: 'c1', delta: ':"
|
|
31547
|
+
event(EventType.TOOL_CALL_ARGS, { toolCallId: 'c1', delta: ':"success"}' }),
|
|
31530
31548
|
);
|
|
31531
31549
|
const answer = await tools.receive(
|
|
31532
31550
|
event(EventType.TOOL_CALL_END, { toolCallId: 'c1' }),
|
|
31533
31551
|
);
|
|
31534
31552
|
|
|
31535
|
-
expect(ran).toEqual([{ id: '${w.id}.hello', args: {
|
|
31536
|
-
expect(answer?.content).toBe('
|
|
31553
|
+
expect(ran).toEqual([{ id: '${w.id}.hello', args: { tone: 'success' } }]);
|
|
31554
|
+
expect(JSON.parse(answer?.content ?? '{}').tone).toBe('success');
|
|
31537
31555
|
expect(answer?.error).toBeUndefined();
|
|
31538
31556
|
});
|
|
31539
31557
|
|
|
@@ -31567,6 +31585,7 @@ function agentSurfaceBlock(w) {
|
|
|
31567
31585
|
` title: '${w.id}.agent.title',`,
|
|
31568
31586
|
` icon: '${w.id}',`,
|
|
31569
31587
|
" docks: ['right-panel'],",
|
|
31588
|
+
" padded: true,",
|
|
31570
31589
|
` component: ${w.className}AgentPanel,`,
|
|
31571
31590
|
" });"
|
|
31572
31591
|
].join("\n");
|
|
@@ -31738,7 +31757,9 @@ function i18nBundle(w) {
|
|
|
31738
31757
|
}
|
|
31739
31758
|
if (w.features.command) {
|
|
31740
31759
|
bundle["action"] = `${w.name} action`;
|
|
31741
|
-
bundle["actionDescription"] = `Shows a short ${w.name} message.`;
|
|
31760
|
+
bundle["actionDescription"] = `Shows a short ${w.name} message in the tone the caller chooses.`;
|
|
31761
|
+
bundle["actionTone"] = "How the message is shown: info, success or warning. Use info unless the caller asked for emphasis.";
|
|
31762
|
+
bundle["actionAnswers"] = "The tone the message was shown in.";
|
|
31742
31763
|
}
|
|
31743
31764
|
if (w.features.about) bundle["about"] = `About ${w.name}`;
|
|
31744
31765
|
if (w.features.agent) {
|
|
@@ -31760,6 +31781,38 @@ function i18nFile(w) {
|
|
|
31760
31781
|
return JSON.stringify(i18nBundle(w), null, 2) + "\n";
|
|
31761
31782
|
}
|
|
31762
31783
|
|
|
31784
|
+
// ../devkit/src/recipes/angular-weaver/weaver-command.ts
|
|
31785
|
+
function commandBlock(w) {
|
|
31786
|
+
return [
|
|
31787
|
+
" ctx.registerCommand({",
|
|
31788
|
+
` id: '${w.id}.hello',`,
|
|
31789
|
+
` title: '${w.id}.action',`,
|
|
31790
|
+
` description: '${w.id}.actionDescription',`,
|
|
31791
|
+
" arguments: [",
|
|
31792
|
+
` { name: 'tone', kind: 'choice', choices: ['info', 'success', 'warning'], description: '${w.id}.actionTone' },`,
|
|
31793
|
+
" ],",
|
|
31794
|
+
` answers: '${w.id}.actionAnswers',`,
|
|
31795
|
+
` shortcut: '${w.features.shortcut}',`,
|
|
31796
|
+
" callable: true,",
|
|
31797
|
+
" run: (_context, args) => {",
|
|
31798
|
+
" const tone = toneOf(args?.['tone']);",
|
|
31799
|
+
` ctx.ui.toast({ message: '${w.id}.action', kind: tone });`,
|
|
31800
|
+
" return { tone };",
|
|
31801
|
+
" },",
|
|
31802
|
+
" });"
|
|
31803
|
+
].join("\n");
|
|
31804
|
+
}
|
|
31805
|
+
function toneHelper() {
|
|
31806
|
+
return [
|
|
31807
|
+
"type Tone = 'info' | 'success' | 'warning';",
|
|
31808
|
+
"",
|
|
31809
|
+
"// The workbench already refused anything outside the declared choices; this narrows the type.",
|
|
31810
|
+
"function toneOf(value: unknown): Tone {",
|
|
31811
|
+
" return value === 'success' || value === 'warning' ? value : 'info';",
|
|
31812
|
+
"}"
|
|
31813
|
+
].join("\n");
|
|
31814
|
+
}
|
|
31815
|
+
|
|
31763
31816
|
// ../devkit/src/recipes/angular-weaver/recipe.ts
|
|
31764
31817
|
var DEFAULT_MENU_SLOT = "content/tab/context";
|
|
31765
31818
|
var SURFACE_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="M8 9h8M8 13h8M8 17h5"/></svg>';
|
|
@@ -31938,18 +31991,6 @@ function railBlock(w) {
|
|
|
31938
31991
|
lines.push(" });");
|
|
31939
31992
|
return lines.join("\n");
|
|
31940
31993
|
}
|
|
31941
|
-
function commandBlock(w) {
|
|
31942
|
-
return [
|
|
31943
|
-
" ctx.registerCommand({",
|
|
31944
|
-
` id: '${w.id}.hello',`,
|
|
31945
|
-
` title: '${w.id}.action',`,
|
|
31946
|
-
` description: '${w.id}.actionDescription',`,
|
|
31947
|
-
` shortcut: '${w.features.shortcut}',`,
|
|
31948
|
-
" callable: true,",
|
|
31949
|
-
` run: () => ctx.ui.toast({ message: '${w.id}.action', kind: 'info' }),`,
|
|
31950
|
-
" });"
|
|
31951
|
-
].join("\n");
|
|
31952
|
-
}
|
|
31953
31994
|
function barItemBlock(w) {
|
|
31954
31995
|
return [
|
|
31955
31996
|
" ctx.registerBarItem({",
|
|
@@ -32061,9 +32102,10 @@ function pluginFile(w) {
|
|
|
32061
32102
|
deactivate() {
|
|
32062
32103
|
${w.propertyName}Agent.set(null);
|
|
32063
32104
|
},` : "";
|
|
32105
|
+
if (w.features.command) consts.push(toneHelper());
|
|
32064
32106
|
return `${imports.join("\n")}
|
|
32065
32107
|
|
|
32066
|
-
${consts.join("\n")}
|
|
32108
|
+
${consts.join("\n\n")}
|
|
32067
32109
|
|
|
32068
32110
|
export const ${w.propertyName}Plugin: Plugin = {
|
|
32069
32111
|
manifest: {
|
|
@@ -32502,9 +32544,9 @@ function readme2(d) {
|
|
|
32502
32544
|
"project's `project.json`, keeping the targets, `implicitDependencies` and tags it already had.",
|
|
32503
32545
|
"",
|
|
32504
32546
|
"Two leftovers from `ng new` are no longer referenced and can go: `src/app/app.routes.ts` (the",
|
|
32505
|
-
"shell owns content routing via `provideShellRouter()`) and `src/app/app.css`. The
|
|
32506
|
-
"`src/app/app.spec.ts`
|
|
32507
|
-
"
|
|
32547
|
+
"shell owns content routing via `provideShellRouter()`) and `src/app/app.css`. The starter test",
|
|
32548
|
+
"`src/app/app.spec.ts` was replaced by one that boots the shell with this composition root, so",
|
|
32549
|
+
"`ng test` is green as generated; test your own components beside it.",
|
|
32508
32550
|
"",
|
|
32509
32551
|
"The project is generated **untagged**: Nx tags belong to your `depConstraints`, and inventing",
|
|
32510
32552
|
"one would fail a lint policy you never opted this project into. If your workspace enforces",
|
|
@@ -32758,6 +32800,31 @@ describe('layout', () => {
|
|
|
32758
32800
|
});
|
|
32759
32801
|
`;
|
|
32760
32802
|
}
|
|
32803
|
+
function appSpec() {
|
|
32804
|
+
return `import { TestBed } from '@angular/core/testing';
|
|
32805
|
+
import { App } from './app';
|
|
32806
|
+
import { appConfig } from './app.config';
|
|
32807
|
+
|
|
32808
|
+
/* The application's starter test, rewritten for a root component that renders the shell: it boots
|
|
32809
|
+
with the composition root's own providers, so whatever you compose in is what the test boots
|
|
32810
|
+
with, and it asserts only that the shell mounted. */
|
|
32811
|
+
describe('App', () => {
|
|
32812
|
+
beforeEach(async () => {
|
|
32813
|
+
await TestBed.configureTestingModule({
|
|
32814
|
+
imports: [App],
|
|
32815
|
+
providers: appConfig.providers,
|
|
32816
|
+
}).compileComponents();
|
|
32817
|
+
});
|
|
32818
|
+
|
|
32819
|
+
it('boots the shell', async () => {
|
|
32820
|
+
const fixture = TestBed.createComponent(App);
|
|
32821
|
+
await fixture.whenStable();
|
|
32822
|
+
const compiled = fixture.nativeElement as HTMLElement;
|
|
32823
|
+
expect(compiled.querySelector('lw-shell')).toBeTruthy();
|
|
32824
|
+
});
|
|
32825
|
+
});
|
|
32826
|
+
`;
|
|
32827
|
+
}
|
|
32761
32828
|
function indexHtml(d) {
|
|
32762
32829
|
return `<!DOCTYPE html>
|
|
32763
32830
|
<html lang="en">
|
|
@@ -32884,7 +32951,10 @@ var angularDistribution = {
|
|
|
32884
32951
|
return {
|
|
32885
32952
|
"src/main.ts": mainTs(),
|
|
32886
32953
|
"src/app/app.config.ts": appConfigTs(d),
|
|
32887
|
-
...d.withTests && {
|
|
32954
|
+
...d.withTests && {
|
|
32955
|
+
"src/app/app.config.spec.ts": appConfigSpec(),
|
|
32956
|
+
"src/app/app.spec.ts": appSpec()
|
|
32957
|
+
},
|
|
32888
32958
|
"src/app/app.ts": appTs(),
|
|
32889
32959
|
"src/app/app.html": appHtml(),
|
|
32890
32960
|
"src/index.html": indexHtml(d),
|
|
@@ -33124,6 +33194,8 @@ function bootstrapFile(t) {
|
|
|
33124
33194
|
--lw-on-info: #000;
|
|
33125
33195
|
|
|
33126
33196
|
--lw-scrim: rgb(0 0 0 / 0.5);
|
|
33197
|
+
--lw-scroll-thumb: rgb(0 0 0 / 0.3);
|
|
33198
|
+
--lw-scroll-track: transparent;
|
|
33127
33199
|
|
|
33128
33200
|
--lw-font-sans: var(--bs-body-font-family);
|
|
33129
33201
|
--lw-font-mono: var(--bs-font-monospace);
|
|
@@ -33797,6 +33869,184 @@ function validateCatalog(catalog, known = KNOWN_CAPABILITIES) {
|
|
|
33797
33869
|
return findings;
|
|
33798
33870
|
}
|
|
33799
33871
|
|
|
33872
|
+
// ../devkit/src/lib/validate/commands.ts
|
|
33873
|
+
var RUNTIME_NOTE = "Grants, access and the window decide what is finally offered at runtime; this check judged the registrations alone.";
|
|
33874
|
+
function validateCommands(sources, ts) {
|
|
33875
|
+
const findings = [];
|
|
33876
|
+
for (const source of sources) {
|
|
33877
|
+
const file2 = ts.createSourceFile(source.path, source.text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
33878
|
+
for (const call of registerCommandCalls(ts, file2)) {
|
|
33879
|
+
findings.push(...judge(read(ts, file2, call)));
|
|
33880
|
+
}
|
|
33881
|
+
}
|
|
33882
|
+
findings.push({ level: "info", code: "commands.runtime", message: RUNTIME_NOTE });
|
|
33883
|
+
return findings;
|
|
33884
|
+
}
|
|
33885
|
+
function registerCommandCalls(ts, file2) {
|
|
33886
|
+
const calls = [];
|
|
33887
|
+
const visit = (node) => {
|
|
33888
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "registerCommand") {
|
|
33889
|
+
calls.push(node);
|
|
33890
|
+
}
|
|
33891
|
+
ts.forEachChild(node, visit);
|
|
33892
|
+
};
|
|
33893
|
+
visit(file2);
|
|
33894
|
+
return calls;
|
|
33895
|
+
}
|
|
33896
|
+
function read(ts, file2, call) {
|
|
33897
|
+
const line = file2.getLineAndCharacterOfPosition(call.getStart(file2)).line + 1;
|
|
33898
|
+
const at2 = `${file2.fileName}:${line}`;
|
|
33899
|
+
const literal2 = call.arguments[0];
|
|
33900
|
+
const empty = { at: at2, callable: false, described: false, answers: false, returnsValue: false, arguments: [] };
|
|
33901
|
+
if (!literal2 || !ts.isObjectLiteralExpression(literal2)) {
|
|
33902
|
+
return { ...empty, unreadable: "its argument is not an object literal" };
|
|
33903
|
+
}
|
|
33904
|
+
const properties = /* @__PURE__ */ new Map();
|
|
33905
|
+
for (const property of literal2.properties) {
|
|
33906
|
+
if (ts.isSpreadAssignment(property)) {
|
|
33907
|
+
return { ...empty, unreadable: "it spreads another value into the registration" };
|
|
33908
|
+
}
|
|
33909
|
+
if (ts.isPropertyAssignment(property) && (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name))) {
|
|
33910
|
+
properties.set(property.name.text, property.initializer);
|
|
33911
|
+
} else if (ts.isMethodDeclaration(property) && ts.isIdentifier(property.name)) {
|
|
33912
|
+
properties.set(property.name.text, property);
|
|
33913
|
+
}
|
|
33914
|
+
}
|
|
33915
|
+
const id = properties.get("id");
|
|
33916
|
+
if (!id || !ts.isStringLiteral(id)) {
|
|
33917
|
+
return { ...empty, unreadable: "its id is not a string literal" };
|
|
33918
|
+
}
|
|
33919
|
+
const callable = properties.get("callable");
|
|
33920
|
+
return {
|
|
33921
|
+
at: at2,
|
|
33922
|
+
id: id.text,
|
|
33923
|
+
callable: callable !== void 0 && callable.kind === ts.SyntaxKind.TrueKeyword,
|
|
33924
|
+
described: properties.has("description"),
|
|
33925
|
+
answers: properties.has("answers"),
|
|
33926
|
+
returnsValue: returnsValue(ts, properties.get("run")),
|
|
33927
|
+
arguments: readArguments(ts, properties.get("arguments"))
|
|
33928
|
+
};
|
|
33929
|
+
}
|
|
33930
|
+
function readArguments(ts, value) {
|
|
33931
|
+
if (!value || !ts.isArrayLiteralExpression(value)) {
|
|
33932
|
+
return [];
|
|
33933
|
+
}
|
|
33934
|
+
return value.elements.map((element, index) => {
|
|
33935
|
+
if (!ts.isObjectLiteralExpression(element)) {
|
|
33936
|
+
return { name: `#${index + 1}`, described: false };
|
|
33937
|
+
}
|
|
33938
|
+
let name = `#${index + 1}`;
|
|
33939
|
+
let described = false;
|
|
33940
|
+
for (const property of element.properties) {
|
|
33941
|
+
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) {
|
|
33942
|
+
continue;
|
|
33943
|
+
}
|
|
33944
|
+
if (property.name.text === "name" && ts.isStringLiteral(property.initializer)) {
|
|
33945
|
+
name = property.initializer.text;
|
|
33946
|
+
}
|
|
33947
|
+
if (property.name.text === "description") {
|
|
33948
|
+
described = true;
|
|
33949
|
+
}
|
|
33950
|
+
}
|
|
33951
|
+
return { name, described };
|
|
33952
|
+
});
|
|
33953
|
+
}
|
|
33954
|
+
function returnsValue(ts, run) {
|
|
33955
|
+
if (!run) {
|
|
33956
|
+
return false;
|
|
33957
|
+
}
|
|
33958
|
+
if (ts.isArrowFunction(run) && !ts.isBlock(run.body)) {
|
|
33959
|
+
return ts.isObjectLiteralExpression(run.body) || ts.isArrayLiteralExpression(run.body);
|
|
33960
|
+
}
|
|
33961
|
+
let found = false;
|
|
33962
|
+
const visit = (node) => {
|
|
33963
|
+
if (found || ts.isFunctionLike(node) && node !== run) {
|
|
33964
|
+
return;
|
|
33965
|
+
}
|
|
33966
|
+
if (ts.isReturnStatement(node) && node.expression && node.expression.kind !== ts.SyntaxKind.UndefinedKeyword) {
|
|
33967
|
+
found = true;
|
|
33968
|
+
return;
|
|
33969
|
+
}
|
|
33970
|
+
ts.forEachChild(node, visit);
|
|
33971
|
+
};
|
|
33972
|
+
visit(run);
|
|
33973
|
+
return found;
|
|
33974
|
+
}
|
|
33975
|
+
function judge(registration) {
|
|
33976
|
+
const { at: at2, id } = registration;
|
|
33977
|
+
if (registration.unreadable) {
|
|
33978
|
+
return [
|
|
33979
|
+
{
|
|
33980
|
+
level: "info",
|
|
33981
|
+
code: "command.unreadable",
|
|
33982
|
+
path: at2,
|
|
33983
|
+
message: `A registerCommand call at ${at2} could not be read: ${registration.unreadable}. What it offers an agent is unknown here.`
|
|
33984
|
+
}
|
|
33985
|
+
];
|
|
33986
|
+
}
|
|
33987
|
+
if (!registration.callable) {
|
|
33988
|
+
return [
|
|
33989
|
+
{
|
|
33990
|
+
level: "info",
|
|
33991
|
+
code: "command.private",
|
|
33992
|
+
path: at2,
|
|
33993
|
+
message: `${id}: not offered to an agent, because it is not callable; an agent never sees it.`
|
|
33994
|
+
}
|
|
33995
|
+
];
|
|
33996
|
+
}
|
|
33997
|
+
const findings = [];
|
|
33998
|
+
if (!registration.described) {
|
|
33999
|
+
findings.push({
|
|
34000
|
+
level: "warning",
|
|
34001
|
+
code: "command.description",
|
|
34002
|
+
path: at2,
|
|
34003
|
+
message: `${id}: offered to an agent without a description; the agent sees only the id and has to guess what it does.`
|
|
34004
|
+
});
|
|
34005
|
+
}
|
|
34006
|
+
for (const argument of registration.arguments) {
|
|
34007
|
+
if (!argument.described) {
|
|
34008
|
+
findings.push({
|
|
34009
|
+
level: "info",
|
|
34010
|
+
code: "command.argument",
|
|
34011
|
+
path: at2,
|
|
34012
|
+
message: `${id}: argument "${argument.name}" has no description; an agent sees a bare name and guesses its value.`
|
|
34013
|
+
});
|
|
34014
|
+
}
|
|
34015
|
+
}
|
|
34016
|
+
if (registration.returnsValue && !registration.answers) {
|
|
34017
|
+
findings.push({
|
|
34018
|
+
level: "info",
|
|
34019
|
+
code: "command.answers",
|
|
34020
|
+
path: at2,
|
|
34021
|
+
message: `${id}: run returns a value but declares no answers; an agent hears that the command ran, not what it answered.`
|
|
34022
|
+
});
|
|
34023
|
+
}
|
|
34024
|
+
if (findings.length === 0) {
|
|
34025
|
+
const count = registration.arguments.length;
|
|
34026
|
+
findings.push({
|
|
34027
|
+
level: "info",
|
|
34028
|
+
code: "command.offered",
|
|
34029
|
+
path: at2,
|
|
34030
|
+
message: `${id}: offered to an agent, with ${count} described argument${count === 1 ? "" : "s"}${registration.answers ? " and a declared answer" : ""}.`
|
|
34031
|
+
});
|
|
34032
|
+
}
|
|
34033
|
+
return findings;
|
|
34034
|
+
}
|
|
34035
|
+
|
|
34036
|
+
// ../devkit/src/lib/validate/typescript.ts
|
|
34037
|
+
import { createRequire } from "node:module";
|
|
34038
|
+
import { resolve } from "node:path";
|
|
34039
|
+
function loadTypeScript(fromDir) {
|
|
34040
|
+
for (const from of [resolve(fromDir, "package.json"), resolve(process.cwd(), "package.json")]) {
|
|
34041
|
+
try {
|
|
34042
|
+
return createRequire(from)("typescript");
|
|
34043
|
+
} catch {
|
|
34044
|
+
continue;
|
|
34045
|
+
}
|
|
34046
|
+
}
|
|
34047
|
+
return null;
|
|
34048
|
+
}
|
|
34049
|
+
|
|
33800
34050
|
// src/lib/tools.ts
|
|
33801
34051
|
function ok(data) {
|
|
33802
34052
|
return {
|
|
@@ -33852,6 +34102,23 @@ function validateI18nTool(a) {
|
|
|
33852
34102
|
const bundles = a["bundles"] ?? {};
|
|
33853
34103
|
return ok({ findings: validateI18nParity(bundles) });
|
|
33854
34104
|
}
|
|
34105
|
+
function validateCommandsTool(a) {
|
|
34106
|
+
const files = a["files"] ?? {};
|
|
34107
|
+
const ts = loadTypeScript(process.cwd());
|
|
34108
|
+
if (!ts) {
|
|
34109
|
+
return ok({
|
|
34110
|
+
findings: [
|
|
34111
|
+
{
|
|
34112
|
+
level: "error",
|
|
34113
|
+
code: "commands.typescript",
|
|
34114
|
+
message: "typescript is not installed where this server runs; the check reads sources with the TypeScript compiler API, so start the server inside the project."
|
|
34115
|
+
}
|
|
34116
|
+
]
|
|
34117
|
+
});
|
|
34118
|
+
}
|
|
34119
|
+
const sources = Object.entries(files).map(([path, text]) => ({ path, text }));
|
|
34120
|
+
return ok({ findings: validateCommands(sources, ts), note: RUNTIME_NOTE });
|
|
34121
|
+
}
|
|
33855
34122
|
|
|
33856
34123
|
// src/lib/server.ts
|
|
33857
34124
|
var FILE_MAP_NOTE = 'Returns a file map (relative path -> content); write the files into your project. May also return "remaining": steps the workspace needs that this route cannot perform, each saying what it costs to skip. Carry them out, or the generated output builds and does not work.';
|
|
@@ -33873,7 +34140,7 @@ function inputSchema(descriptor) {
|
|
|
33873
34140
|
function createMcpServer() {
|
|
33874
34141
|
const server = new McpServer({
|
|
33875
34142
|
name: "loomweaver-devkit",
|
|
33876
|
-
version: "0.
|
|
34143
|
+
version: "0.9.0-preview.10"
|
|
33877
34144
|
});
|
|
33878
34145
|
server.registerTool(
|
|
33879
34146
|
"list_generators",
|
|
@@ -33915,6 +34182,16 @@ function createMcpServer() {
|
|
|
33915
34182
|
},
|
|
33916
34183
|
async (args) => validateI18nTool(args)
|
|
33917
34184
|
);
|
|
34185
|
+
server.registerTool(
|
|
34186
|
+
"validate_commands",
|
|
34187
|
+
{
|
|
34188
|
+
description: "Say, per command a plugin registers, whether an agent is offered it and what would leave the agent guessing. Input: files = { <path>: <TypeScript source> }. Only a callable command without a description is a warning; the rest is information.",
|
|
34189
|
+
inputSchema: {
|
|
34190
|
+
files: external_exports.record(external_exports.string(), external_exports.string())
|
|
34191
|
+
}
|
|
34192
|
+
},
|
|
34193
|
+
async (args) => validateCommandsTool(args)
|
|
34194
|
+
);
|
|
33918
34195
|
server.registerTool(
|
|
33919
34196
|
"validate_catalog",
|
|
33920
34197
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomweaver/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-preview.10",
|
|
4
4
|
"description": "LoomWeaver: open-source plugin platform for Angular workbenches. The MCP server: scaffolding and validation as tools, so an AI assistant in any product repo can build weavers without a checkout.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"loomweaver",
|