Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
"codebase-memory-mcp": {
"command": "codebase-memory-mcp",
"args": []
},
"puppeteer": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-puppeteer@2025.5.12"
]
}
}
}
325 changes: 269 additions & 56 deletions apps/api/README.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions apps/api/mcp/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { PluginRegistry } from "./registry.js";
import { ToolsDiscovery } from "./discovery.js";
import { ToolExecutor } from "./execute.js";
import { fileURLToPath } from "url";
import { fileManagerPlugin } from "./plugins/filemanager/manifest.js";
import { context7Plugin } from "./plugins/context7/manifest.js";
import { puppeteerPlugin } from "./plugins/puppeteer/manifest.js";

export const mcpRegistry = new PluginRegistry();

// Register plugins from modular layout manifests
mcpRegistry.registerPlugin(fileManagerPlugin);
mcpRegistry.registerPlugin(context7Plugin);
mcpRegistry.registerPlugin(puppeteerPlugin);

export const mcpDiscovery = new ToolsDiscovery(mcpRegistry);
export const mcpExecutor = new ToolExecutor(mcpDiscovery);
2 changes: 1 addition & 1 deletion apps/api/mcp/plugins/context7/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import "../../../../src/utils/env.js";
import "../../../src/utils/env.js";
import { StdioMCPServer } from "../../stdio-server.js";

const context7Env: Record<string, string> = {};
Expand Down
9 changes: 9 additions & 0 deletions apps/api/mcp/plugins/puppeteer/manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import "../../../src/utils/env.js";
import { StdioMCPServer } from "../../stdio-server.js";

export const puppeteerPlugin = new StdioMCPServer(
"puppeteer",
"npx",
["-y", "@modelcontextprotocol/server-puppeteer"],
{}
);
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.4.0",
"@modelcontextprotocol/server-puppeteer": "^2025.5.12",
"@repo/db": "*",
"@repo/shared": "*",
"cors": "^2.8.5",
Expand Down
87 changes: 86 additions & 1 deletion apps/api/src/agent/agent.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
import { runAgent } from "./loop.js";
import { createMemory } from "./memory.js";
import { llmClient } from "./llm.js";
import { llmClient, nextStep } from "./llm.js";

// Mock @repo/db
vi.mock("@repo/db", () => {
Expand Down Expand Up @@ -425,6 +425,91 @@ describe("Agent Module & Execution Loop", () => {
expect(result.reason).toBe("Approval not approved");
});

// 14) Parallel Tool Execution tests
describe("Parallel Tool Execution", () => {
it("should parse type: tool_calls from LLM and validate schemas", async () => {
vi.spyOn(llmClient, "callModel").mockResolvedValue(
JSON.stringify({
type: "tool_calls",
tool_calls: [
{ tool_name: "test_tool", arguments: { arg1: "val1" } },
{ tool_name: "test_tool", arguments: { arg1: "val2" } }
]
})
);

const memory = createMemory();
const tools = [
{
name: "test_tool",
description: "A test tool",
inputSchema: { type: "object", properties: { arg1: { type: "string" } } },
execute: vi.fn(),
}
];

const res = await nextStep(memory, tools);
expect(res.step.type).toBe("tool_calls");
if (res.step.type === "tool_calls") {
expect(res.step.tool_calls).toHaveLength(2);
expect(res.step.tool_calls[0]?.tool_name).toBe("test_tool");
}
});

it("should execute parallel tool calls successfully when allowed", async () => {
let callCount = 0;
vi.spyOn(llmClient, "callModel").mockImplementation(async () => {
callCount++;
if (callCount === 1) {
return JSON.stringify({
type: "tool_calls",
tool_calls: [
{ tool_name: "test_tool", arguments: { arg1: "val1" } },
{ tool_name: "test_tool", arguments: { arg1: "val2" } }
]
});
}
return JSON.stringify({
type: "final_answer",
answer: "Finished parallel work.",
});
});

vi.mocked(decide).mockResolvedValue({
decision: "ALLOW",
});

vi.mocked(mcpExecutor.execute).mockResolvedValue("mockResult");

const result = await runAgent("Do parallel tasks", "conv-parallel-1");
expect(result.status).toBe("SUCCESS");
expect(result.answer).toBe("Finished parallel work.");
expect(mcpExecutor.execute).toHaveBeenCalledTimes(2);
expect(result.memory.toolResults).toContain("mockResult");
});

it("should request approval for parallel tool calls when pending", async () => {
vi.spyOn(llmClient, "callModel").mockResolvedValue(
JSON.stringify({
type: "tool_calls",
tool_calls: [
{ tool_name: "test_tool", arguments: { arg1: "val1" } }
]
})
);

vi.mocked(decide).mockResolvedValue({
decision: "PENDING",
reason: "approval-parallel-123",
});

const result = await runAgent("Do parallel task requiring approval", "conv-parallel-2");
expect(result.status).toBe("PENDING");
expect(result.approvalId).toBe("approval-parallel-123");
expect(result.memory.approvalId).toBe("approval-parallel-123");
});
});

describe("Gemini API Client Timeout", () => {
afterEach(() => {
vi.unstubAllGlobals();
Expand Down
Loading
Loading