@loomweaver/mcp 0.14.3 → 0.14.4
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 +66 -84
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -31154,9 +31154,9 @@ function renderRegions(indent) {
|
|
|
31154
31154
|
// ../devkit/src/recipes/angular-weaver/agent-panel.ts
|
|
31155
31155
|
function panelFile(weaver) {
|
|
31156
31156
|
return `import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
31157
|
-
import { EventType, type
|
|
31158
|
-
import {
|
|
31159
|
-
import {
|
|
31157
|
+
import { EventType, type AGUIEvent, type Tool, type ToolMessage } from '@ag-ui/core';
|
|
31158
|
+
import { askAgent } from './${weaver.id}-agent';
|
|
31159
|
+
import { ${weaver.propertyName}Tools } from './${weaver.id}-connection';
|
|
31160
31160
|
|
|
31161
31161
|
interface Line {
|
|
31162
31162
|
readonly kind: 'you' | 'agent' | 'call' | 'result' | 'note';
|
|
@@ -31186,11 +31186,11 @@ export class ${weaver.className}AgentPanel {
|
|
|
31186
31186
|
// The list is read again whenever the panel gains focus, because the weaver's own translations
|
|
31187
31187
|
// and other plugins' commands may arrive after this component was built.
|
|
31188
31188
|
protected refreshOffered(): void {
|
|
31189
|
-
this.offered.set(${weaver.propertyName}
|
|
31189
|
+
this.offered.set(${weaver.propertyName}Tools()?.list() ?? []);
|
|
31190
31190
|
}
|
|
31191
31191
|
|
|
31192
31192
|
protected async ask(name: string): Promise<void> {
|
|
31193
|
-
const tools = ${weaver.propertyName}
|
|
31193
|
+
const tools = ${weaver.propertyName}Tools();
|
|
31194
31194
|
if (!tools || this.busy()) {
|
|
31195
31195
|
return;
|
|
31196
31196
|
}
|
|
@@ -31233,24 +31233,19 @@ export class ${weaver.className}AgentPanel {
|
|
|
31233
31233
|
}
|
|
31234
31234
|
}
|
|
31235
31235
|
|
|
31236
|
-
private draw(event:
|
|
31237
|
-
const raw = event as unknown as Record<string, unknown>;
|
|
31236
|
+
private draw(event: AGUIEvent): void {
|
|
31238
31237
|
switch (event.type) {
|
|
31239
31238
|
case EventType.TEXT_MESSAGE_START:
|
|
31240
31239
|
this.push({ kind: 'agent', text: '' });
|
|
31241
31240
|
break;
|
|
31242
31241
|
case EventType.TEXT_MESSAGE_CONTENT:
|
|
31243
|
-
this.grow('text',
|
|
31242
|
+
this.grow('text', event.delta);
|
|
31244
31243
|
break;
|
|
31245
31244
|
case EventType.TOOL_CALL_START:
|
|
31246
|
-
this.push({
|
|
31247
|
-
kind: 'call',
|
|
31248
|
-
text: String(raw['toolCallName'] ?? ''),
|
|
31249
|
-
args: '',
|
|
31250
|
-
});
|
|
31245
|
+
this.push({ kind: 'call', text: event.toolCallName, args: '' });
|
|
31251
31246
|
break;
|
|
31252
31247
|
case EventType.TOOL_CALL_ARGS:
|
|
31253
|
-
this.grow('args',
|
|
31248
|
+
this.grow('args', event.delta);
|
|
31254
31249
|
break;
|
|
31255
31250
|
default:
|
|
31256
31251
|
break;
|
|
@@ -31279,7 +31274,7 @@ function panelTemplateFile(weaver) {
|
|
|
31279
31274
|
class="shrink-0 rounded-md border border-border bg-surface-raised px-3 py-2 text-xs text-content-muted"
|
|
31280
31275
|
>
|
|
31281
31276
|
This is a stand-in, not an assistant. It speaks the protocol so you can watch the whole path;
|
|
31282
|
-
replace <code class="text-content">${weaver.id}-agent
|
|
31277
|
+
replace <code class="text-content">${weaver.id}-agent.ts</code> with your own transport.
|
|
31283
31278
|
</p>
|
|
31284
31279
|
|
|
31285
31280
|
<div class="min-h-0 flex-1 overflow-auto">
|
|
@@ -31347,7 +31342,7 @@ function panelTemplateFile(weaver) {
|
|
|
31347
31342
|
|
|
31348
31343
|
// ../devkit/src/recipes/angular-weaver/agent-stand-in.ts
|
|
31349
31344
|
function standInFile(weaver) {
|
|
31350
|
-
return `import { EventType, type
|
|
31345
|
+
return `import { EventType, type AGUIEvent, type Tool } from '@ag-ui/core';
|
|
31351
31346
|
|
|
31352
31347
|
// WHAT THIS IS: a stand-in for an agent, and not an agent. It speaks the AG-UI protocol and nothing
|
|
31353
31348
|
// else \u2014 no model, no network, no judgement \u2014 so that the whole path from the offered tools through
|
|
@@ -31367,15 +31362,16 @@ export interface AgentRequest {
|
|
|
31367
31362
|
|
|
31368
31363
|
export async function* askAgent(
|
|
31369
31364
|
request: AgentRequest,
|
|
31370
|
-
): AsyncGenerator<
|
|
31365
|
+
): AsyncGenerator<AGUIEvent> {
|
|
31371
31366
|
const picked = request.tools.find((tool) =>
|
|
31372
31367
|
request.prompt.includes(tool.name),
|
|
31373
31368
|
);
|
|
31374
31369
|
|
|
31375
|
-
yield
|
|
31370
|
+
yield {
|
|
31371
|
+
type: EventType.RUN_STARTED,
|
|
31376
31372
|
threadId: '${weaver.id}-stand-in',
|
|
31377
31373
|
runId: request.runId,
|
|
31378
|
-
}
|
|
31374
|
+
};
|
|
31379
31375
|
yield* speak(
|
|
31380
31376
|
\`\${request.runId}.says\`,
|
|
31381
31377
|
picked
|
|
@@ -31385,37 +31381,35 @@ export async function* askAgent(
|
|
|
31385
31381
|
|
|
31386
31382
|
if (picked) {
|
|
31387
31383
|
const toolCallId = \`\${request.runId}.call\`;
|
|
31388
|
-
yield
|
|
31389
|
-
toolCallId,
|
|
31390
|
-
toolCallName: picked.name,
|
|
31391
|
-
});
|
|
31384
|
+
yield { type: EventType.TOOL_CALL_START, toolCallId, toolCallName: picked.name };
|
|
31392
31385
|
// Arguments arrive in pieces, exactly as they do from a real model. The adapter assembles them;
|
|
31393
31386
|
// nothing downstream ever sees a half-written call. The values come from the JSON Schema the
|
|
31394
31387
|
// workbench described in picked.parameters: the first declared choice of the first argument that
|
|
31395
31388
|
// has choices, and nothing for the rest, so the command applies its own defaults there.
|
|
31396
31389
|
for (const piece of chunks(JSON.stringify(sampleArguments(picked)), 4)) {
|
|
31397
|
-
yield
|
|
31390
|
+
yield { type: EventType.TOOL_CALL_ARGS, toolCallId, delta: piece };
|
|
31398
31391
|
await pause();
|
|
31399
31392
|
}
|
|
31400
|
-
yield
|
|
31393
|
+
yield { type: EventType.TOOL_CALL_END, toolCallId };
|
|
31401
31394
|
}
|
|
31402
31395
|
|
|
31403
|
-
yield
|
|
31396
|
+
yield {
|
|
31397
|
+
type: EventType.RUN_FINISHED,
|
|
31404
31398
|
threadId: '${weaver.id}-stand-in',
|
|
31405
31399
|
runId: request.runId,
|
|
31406
|
-
}
|
|
31400
|
+
};
|
|
31407
31401
|
}
|
|
31408
31402
|
|
|
31409
31403
|
async function* speak(
|
|
31410
31404
|
messageId: string,
|
|
31411
31405
|
text: string,
|
|
31412
|
-
): AsyncGenerator<
|
|
31413
|
-
yield
|
|
31406
|
+
): AsyncGenerator<AGUIEvent> {
|
|
31407
|
+
yield { type: EventType.TEXT_MESSAGE_START, messageId, role: 'assistant' };
|
|
31414
31408
|
for (const piece of chunks(text, 10)) {
|
|
31415
|
-
yield
|
|
31409
|
+
yield { type: EventType.TEXT_MESSAGE_CONTENT, messageId, delta: piece };
|
|
31416
31410
|
await pause();
|
|
31417
31411
|
}
|
|
31418
|
-
yield
|
|
31412
|
+
yield { type: EventType.TEXT_MESSAGE_END, messageId };
|
|
31419
31413
|
}
|
|
31420
31414
|
|
|
31421
31415
|
function sampleArguments(tool: Tool): Record<string, unknown> {
|
|
@@ -31438,10 +31432,6 @@ function chunks(text: string, size: number): readonly string[] {
|
|
|
31438
31432
|
return pieces;
|
|
31439
31433
|
}
|
|
31440
31434
|
|
|
31441
|
-
function event(type: EventType, fields: Record<string, unknown>): BaseEvent {
|
|
31442
|
-
return { type, ...fields } as unknown as BaseEvent;
|
|
31443
|
-
}
|
|
31444
|
-
|
|
31445
31435
|
function pause(): Promise<void> {
|
|
31446
31436
|
return new Promise((done) => setTimeout(done, PAUSE_MS));
|
|
31447
31437
|
}
|
|
@@ -31460,15 +31450,18 @@ import {
|
|
|
31460
31450
|
} from '@loomweaver/ag-ui';
|
|
31461
31451
|
import type { PluginContext } from '@loomweaver/plugin-sdk';
|
|
31462
31452
|
|
|
31453
|
+
// The connection is the workbench's half: the commands offered as tools, and each call put to the
|
|
31454
|
+
// workbench and answered. The agent is yours and lives beside it, in ${weaver.id}-agent.ts.
|
|
31455
|
+
//
|
|
31463
31456
|
// A factory, not a module-level connection: everything a run needs lives in the closure, so a second
|
|
31464
31457
|
// one never shares state with the first.
|
|
31465
|
-
export function ${weaver.
|
|
31458
|
+
export function connect${weaver.className}(ctx: PluginContext): CommandTools {
|
|
31466
31459
|
return commandTools(ctx, { before: (call) => decide(ctx, call) });
|
|
31467
31460
|
}
|
|
31468
31461
|
|
|
31469
31462
|
// The one connection this plugin activates, published for its panel. Set in activate(), cleared in
|
|
31470
31463
|
// deactivate(), so the panel renders an honest empty state either side of that.
|
|
31471
|
-
export const ${weaver.propertyName}
|
|
31464
|
+
export const ${weaver.propertyName}Tools = signal<CommandTools | null>(null);
|
|
31472
31465
|
|
|
31473
31466
|
// What an agent's word is enough for is the command's own statement, declared where the command is
|
|
31474
31467
|
// registered and read off the call here. No list of ids lives beside the commands: a list drifts from
|
|
@@ -31503,9 +31496,10 @@ async function decide(
|
|
|
31503
31496
|
`;
|
|
31504
31497
|
}
|
|
31505
31498
|
function specFile(weaver) {
|
|
31506
|
-
return `import { EventType, type
|
|
31499
|
+
return `import { EventType, type AGUIEvent, type ToolMessage } from '@ag-ui/core';
|
|
31500
|
+
import type { CommandTools } from '@loomweaver/ag-ui';
|
|
31507
31501
|
import type { CommandArguments, PluginContext } from '@loomweaver/plugin-sdk';
|
|
31508
|
-
import { ${weaver.
|
|
31502
|
+
import { connect${weaver.className} } from './${weaver.id}-connection';
|
|
31509
31503
|
|
|
31510
31504
|
interface Asked {
|
|
31511
31505
|
readonly id: string;
|
|
@@ -31526,37 +31520,36 @@ function contextThat(confirms: boolean, ran: Asked[]): PluginContext {
|
|
|
31526
31520
|
} as unknown as PluginContext;
|
|
31527
31521
|
}
|
|
31528
31522
|
|
|
31529
|
-
function
|
|
31530
|
-
return
|
|
31523
|
+
function streamedCall(toolCallId: string, ...deltas: string[]): AGUIEvent[] {
|
|
31524
|
+
return [
|
|
31525
|
+
{ type: EventType.TOOL_CALL_START, toolCallId, toolCallName: '${weaver.id}.hello' },
|
|
31526
|
+
...deltas.map((delta): AGUIEvent => ({ type: EventType.TOOL_CALL_ARGS, toolCallId, delta })),
|
|
31527
|
+
{ type: EventType.TOOL_CALL_END, toolCallId },
|
|
31528
|
+
];
|
|
31531
31529
|
}
|
|
31532
31530
|
|
|
31533
|
-
|
|
31531
|
+
async function answersTo(tools: CommandTools, events: AGUIEvent[]): Promise<ToolMessage[]> {
|
|
31532
|
+
const answers: ToolMessage[] = [];
|
|
31533
|
+
for (const event of events) {
|
|
31534
|
+
const answer = await tools.receive(event);
|
|
31535
|
+
if (answer) {
|
|
31536
|
+
answers.push(answer);
|
|
31537
|
+
}
|
|
31538
|
+
}
|
|
31539
|
+
return answers;
|
|
31540
|
+
}
|
|
31541
|
+
|
|
31542
|
+
describe('connect${weaver.className}', () => {
|
|
31534
31543
|
it('offers what the workbench offers', () => {
|
|
31535
|
-
const tools = ${weaver.
|
|
31544
|
+
const tools = connect${weaver.className}(contextThat(true, []));
|
|
31536
31545
|
expect(tools.list().map((tool) => tool.name)).toEqual(['${weaver.id}.hello']);
|
|
31537
31546
|
});
|
|
31538
31547
|
|
|
31539
31548
|
it('assembles a call from its events and answers with the outcome', async () => {
|
|
31540
31549
|
const ran: Asked[] = [];
|
|
31541
|
-
const tools = ${weaver.
|
|
31550
|
+
const tools = connect${weaver.className}(contextThat(true, ran));
|
|
31542
31551
|
|
|
31543
|
-
|
|
31544
|
-
await tools.receive(
|
|
31545
|
-
event(EventType.TOOL_CALL_START, {
|
|
31546
|
-
toolCallId: 'c1',
|
|
31547
|
-
toolCallName: '${weaver.id}.hello',
|
|
31548
|
-
}),
|
|
31549
|
-
),
|
|
31550
|
-
).toBeNull();
|
|
31551
|
-
await tools.receive(
|
|
31552
|
-
event(EventType.TOOL_CALL_ARGS, { toolCallId: 'c1', delta: '{"tone"' }),
|
|
31553
|
-
);
|
|
31554
|
-
await tools.receive(
|
|
31555
|
-
event(EventType.TOOL_CALL_ARGS, { toolCallId: 'c1', delta: ':"success"}' }),
|
|
31556
|
-
);
|
|
31557
|
-
const answer = await tools.receive(
|
|
31558
|
-
event(EventType.TOOL_CALL_END, { toolCallId: 'c1' }),
|
|
31559
|
-
);
|
|
31552
|
+
const [answer] = await answersTo(tools, streamedCall('c1', '{"tone"', ':"success"}'));
|
|
31560
31553
|
|
|
31561
31554
|
expect(ran).toEqual([{ id: '${weaver.id}.hello', args: { tone: 'success' } }]);
|
|
31562
31555
|
expect(JSON.parse(answer?.content ?? '{}').tone).toBe('success');
|
|
@@ -31565,20 +31558,9 @@ describe('${weaver.propertyName}Connection', () => {
|
|
|
31565
31558
|
|
|
31566
31559
|
it('never reaches the workbench when a consequential call is declined', async () => {
|
|
31567
31560
|
const ran: Asked[] = [];
|
|
31568
|
-
const tools = ${weaver.
|
|
31561
|
+
const tools = connect${weaver.className}(contextThat(false, ran));
|
|
31569
31562
|
|
|
31570
|
-
await tools
|
|
31571
|
-
event(EventType.TOOL_CALL_START, {
|
|
31572
|
-
toolCallId: 'c2',
|
|
31573
|
-
toolCallName: '${weaver.id}.hello',
|
|
31574
|
-
}),
|
|
31575
|
-
);
|
|
31576
|
-
await tools.receive(
|
|
31577
|
-
event(EventType.TOOL_CALL_ARGS, { toolCallId: 'c2', delta: '{}' }),
|
|
31578
|
-
);
|
|
31579
|
-
const answer = await tools.receive(
|
|
31580
|
-
event(EventType.TOOL_CALL_END, { toolCallId: 'c2' }),
|
|
31581
|
-
);
|
|
31563
|
+
const [answer] = await answersTo(tools, streamedCall('c2', '{}'));
|
|
31582
31564
|
|
|
31583
31565
|
expect(ran).toEqual([]);
|
|
31584
31566
|
expect(answer?.error).toContain('did not run');
|
|
@@ -31600,13 +31582,13 @@ function agentSurfaceBlock(weaver) {
|
|
|
31600
31582
|
}
|
|
31601
31583
|
function agentFiles(weaver) {
|
|
31602
31584
|
const files = {
|
|
31603
|
-
[`src/lib/agent/${weaver.id}-
|
|
31604
|
-
[`src/lib/agent/${weaver.id}-agent
|
|
31585
|
+
[`src/lib/agent/${weaver.id}-connection.ts`]: connectionFile(weaver),
|
|
31586
|
+
[`src/lib/agent/${weaver.id}-agent.ts`]: standInFile(weaver),
|
|
31605
31587
|
[`src/lib/agent/${weaver.id}-agent-panel.ts`]: panelFile(weaver),
|
|
31606
31588
|
[`src/lib/agent/${weaver.id}-agent-panel.html`]: panelTemplateFile(weaver)
|
|
31607
31589
|
};
|
|
31608
31590
|
if (weaver.features.spec) {
|
|
31609
|
-
files[`src/lib/agent/${weaver.id}-
|
|
31591
|
+
files[`src/lib/agent/${weaver.id}-connection.spec.ts`] = specFile(weaver);
|
|
31610
31592
|
}
|
|
31611
31593
|
return files;
|
|
31612
31594
|
}
|
|
@@ -32067,7 +32049,7 @@ function pluginImports(weaver) {
|
|
|
32067
32049
|
}
|
|
32068
32050
|
if (weaver.features.agent) {
|
|
32069
32051
|
imports.push(
|
|
32070
|
-
`import { ${weaver.
|
|
32052
|
+
`import { connect${weaver.className}, ${weaver.propertyName}Tools } from '../agent/${weaver.id}-connection';`,
|
|
32071
32053
|
`import { ${weaver.className}AgentPanel } from '../agent/${weaver.id}-agent-panel';`
|
|
32072
32054
|
);
|
|
32073
32055
|
}
|
|
@@ -32092,7 +32074,7 @@ function pluginBody(weaver) {
|
|
|
32092
32074
|
const body = [` ctx.contributeIcons({ '${weaver.id}': icon });`];
|
|
32093
32075
|
if (weaver.features.agent) {
|
|
32094
32076
|
body.push(
|
|
32095
|
-
` ${weaver.propertyName}
|
|
32077
|
+
` ${weaver.propertyName}Tools.set(connect${weaver.className}(ctx));`
|
|
32096
32078
|
);
|
|
32097
32079
|
}
|
|
32098
32080
|
if (weaver.features.command) body.push(commandBlock(weaver));
|
|
@@ -32109,7 +32091,7 @@ function pluginFile(weaver) {
|
|
|
32109
32091
|
const body = pluginBody(weaver);
|
|
32110
32092
|
const deactivate = weaver.features.agent ? `
|
|
32111
32093
|
deactivate() {
|
|
32112
|
-
${weaver.propertyName}
|
|
32094
|
+
${weaver.propertyName}Tools.set(null);
|
|
32113
32095
|
},` : "";
|
|
32114
32096
|
return `${pluginImports(weaver).join("\n")}
|
|
32115
32097
|
|
|
@@ -32132,7 +32114,7 @@ function accessLine(weaver) {
|
|
|
32132
32114
|
}
|
|
32133
32115
|
|
|
32134
32116
|
// ../devkit/src/recipes/platform-version.ts
|
|
32135
|
-
var PLATFORM_VERSION = "0.14.
|
|
32117
|
+
var PLATFORM_VERSION = "0.14.4";
|
|
32136
32118
|
|
|
32137
32119
|
// ../devkit/src/recipes/readme-notes.ts
|
|
32138
32120
|
var UNTAGGED_NOTE = [
|
|
@@ -32206,11 +32188,11 @@ function agentNotes(weaver) {
|
|
|
32206
32188
|
"",
|
|
32207
32189
|
`\`src/lib/agent/\` holds three files and one of them is meant to be thrown away.`,
|
|
32208
32190
|
"",
|
|
32209
|
-
`- \`${weaver.id}-
|
|
32191
|
+
`- \`${weaver.id}-connection.ts\` is the connection: the workbench's own commands offered as tools, and a`,
|
|
32210
32192
|
" seam where this weaver decides about a call before it runs. Nothing is registered twice \u2014 the",
|
|
32211
32193
|
" list comes from the workbench, already narrowed by everything that would refuse the call.",
|
|
32212
32194
|
`- \`${weaver.id}-agent-panel.ts\` shows what is offered, the call as it streams, and the outcome.`,
|
|
32213
|
-
`- \`${weaver.id}-agent
|
|
32195
|
+
`- \`${weaver.id}-agent.ts\` is a **stand-in** for the agent, not an assistant: it produces the protocol's own`,
|
|
32214
32196
|
" events so the whole path runs before you have connected anything. Replace that one file with",
|
|
32215
32197
|
" your transport and nothing else changes. No transport, credential or model is generated for",
|
|
32216
32198
|
" you, because none of them can be guessed.",
|
|
@@ -34588,7 +34570,7 @@ function inputSchema(descriptor) {
|
|
|
34588
34570
|
function createMcpServer() {
|
|
34589
34571
|
const server = new McpServer({
|
|
34590
34572
|
name: "loomweaver-devkit",
|
|
34591
|
-
version: "0.14.
|
|
34573
|
+
version: "0.14.4"
|
|
34592
34574
|
});
|
|
34593
34575
|
server.registerTool(
|
|
34594
34576
|
"list_generators",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomweaver/mcp",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
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",
|