mcp-agent-broker 1.0.0 → 1.0.2

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 CHANGED
@@ -1,31 +1,77 @@
1
1
  # MCP Agent Broker
2
2
 
3
- The Model Context Protocol (MCP) Agent Broker is a centralized service designed to orchestrate and manage autonomous coding agents.
3
+ The Model Context Protocol (MCP) Agent Broker is a centralized service designed to orchestrate and manage autonomous coding agents.
4
+
5
+ It acts as a middleware that securely maps AI Agents (Principals) to tasks, enforcing distributed locks (Fencing Tokens) so multiple agents don't step on each other's toes when working on the same repository.
4
6
 
5
7
  ## Features
6
8
  * **MCP Server Integration:** Natively implements the MCP SDK via Server-Sent Events (SSE).
9
+ * **Secure Authentication:** Validates API keys against securely hashed database records.
7
10
  * **Distributed Locking:** Safely hand off tasks between implementer agents and reviewer agents using fencing tokens.
8
11
  * **Transactional Outbox:** Guaranteed delivery of domain events without dual-write issues.
9
12
  * **Event Relay:** Real-time event broadcasting over Redis Pub/Sub.
10
13
 
11
- ## Prerequisites
12
- You will need Docker installed to run the dependencies:
13
- * PostgreSQL
14
- * Redis
15
- * MinIO
14
+ ---
15
+
16
+ ## Getting Started
16
17
 
17
- ## Usage
18
+ ### 1. Start the Infrastructure
19
+ The broker requires PostgreSQL and Redis. We provide a `docker-compose.yml` to spin up everything (including the broker itself) instantly.
18
20
 
19
- 1. Start the infrastructure:
20
21
  ```bash
21
22
  docker compose up -d
22
23
  ```
24
+ *Note: If you are doing local development, you can run `docker compose up -d postgres redis` and then run the app locally using `npm run dev`.*
25
+
26
+ ### 2. Generate an API Key
27
+ To connect to the broker, your Agent must have an API Key. We provide a seed script to generate your first Workspace, Principal, and API Key.
23
28
 
24
- 2. Run the server:
25
29
  ```bash
26
- npm start
30
+ npx tsx seed_api_key.ts
27
31
  ```
28
- The server will bind to `http://localhost:3000/mcp/sse`.
32
+ *Take note of the API Key (e.g. `test-api-key-123`) output by the terminal.*
33
+
34
+ ### 3. Connect your Agents
35
+ Point your MCP-compatible client to the broker's endpoint and pass your API key as a Bearer token.
36
+
37
+ **Programmatic Example (TypeScript):**
38
+ ```typescript
39
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
40
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
41
+
42
+ const transport = new SSEClientTransport(new URL("http://localhost:3000/mcp/sse"), {
43
+ eventSourceInit: {
44
+ headers: { Authorization: "Bearer test-api-key-123" }
45
+ } as any,
46
+ requestInit: {
47
+ headers: { Authorization: "Bearer test-api-key-123" }
48
+ }
49
+ });
50
+
51
+ const client = new Client({ name: "my-agent", version: "1.0.0" }, { capabilities: {} });
52
+ await client.connect(transport);
53
+
54
+ // Register your agent!
55
+ await client.request({
56
+ method: "tools/call",
57
+ params: {
58
+ name: "register_agent",
59
+ arguments: {
60
+ workspaceId: "00000000-0000-0000-0000-000000000000",
61
+ name: "My Implementer Agent",
62
+ version: "1.0",
63
+ capabilities: ["execute"]
64
+ }
65
+ }
66
+ }, CallToolResultSchema);
67
+ ```
68
+
69
+ ### 4. Listen to Real-Time Events
70
+ Any downstream CI dashboard or review agent can connect to the Redis server and subscribe to `workspace:{workspace_id}:events` to see real-time updates as tasks are created and leases are acquired!
71
+
72
+ ---
29
73
 
30
- 3. Connect your Agents:
31
- Point your MCP-compatible client (like Claude Code) to `http://localhost:3000/mcp/sse`. They can now invoke tools like `register_agent`, `create_task`, and `acquire_task_lease`!
74
+ ## Future Improvements for Contributors
75
+ * Implement standard OAuth2 / OIDC for user-facing dashboards.
76
+ * Extend the `Review Module` to enforce reviewer sign-offs before pushing to main branches.
77
+ * Implement the Fencing Token check in a Git Hook or CI/CD layer.
package/dist/app.js CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  "use strict";
3
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
package/dist/bin.js ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const sse_js_1 = require("@modelcontextprotocol/sdk/client/sse.js");
38
+ const readline = __importStar(require("readline"));
39
+ async function runProxy(url, apiKey) {
40
+ const transport = new sse_js_1.SSEClientTransport(new URL(url), {
41
+ eventSourceInit: {
42
+ headers: { Authorization: `Bearer ${apiKey}` }
43
+ },
44
+ requestInit: {
45
+ headers: { Authorization: `Bearer ${apiKey}` }
46
+ }
47
+ });
48
+ transport.onmessage = (message) => {
49
+ process.stdout.write(JSON.stringify(message) + "\n");
50
+ };
51
+ transport.onerror = (error) => {
52
+ console.error("Transport error:", error);
53
+ };
54
+ transport.onclose = () => {
55
+ process.exit(0);
56
+ };
57
+ await transport.start();
58
+ const rl = readline.createInterface({
59
+ input: process.stdin,
60
+ terminal: false
61
+ });
62
+ rl.on("line", async (line) => {
63
+ if (!line.trim())
64
+ return;
65
+ try {
66
+ const message = JSON.parse(line);
67
+ await transport.send(message);
68
+ }
69
+ catch (e) {
70
+ console.error("Error processing line:", e);
71
+ }
72
+ });
73
+ rl.on("close", () => {
74
+ transport.close().catch(() => { });
75
+ process.exit(0);
76
+ });
77
+ }
78
+ const args = process.argv.slice(2);
79
+ const command = args[0];
80
+ if (command === "connect") {
81
+ let url = "http://127.0.0.1:3000/mcp/sse";
82
+ let apiKey = "";
83
+ for (let i = 1; i < args.length; i++) {
84
+ if (args[i] === "--url" && args[i + 1]) {
85
+ url = args[i + 1];
86
+ i++;
87
+ }
88
+ else if (args[i] === "--token" && args[i + 1]) {
89
+ apiKey = args[i + 1];
90
+ i++;
91
+ }
92
+ }
93
+ if (!apiKey) {
94
+ console.error("Missing --token");
95
+ process.exit(1);
96
+ }
97
+ runProxy(url, apiKey).catch((e) => {
98
+ console.error("Proxy error:", e);
99
+ process.exit(1);
100
+ });
101
+ }
102
+ else {
103
+ // Start server
104
+ import("./app.js").catch(e => {
105
+ console.error("Failed to start server:", e);
106
+ process.exit(1);
107
+ });
108
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.idempotencyRecords = exports.outbox = exports.events = exports.leases = exports.taskAttempts = exports.tasks = exports.agentInstances = exports.agentDefinitions = exports.workspaces = exports.leaseTypeEnum = exports.leaseStatusEnum = exports.agentStatusEnum = exports.workspaceStatusEnum = exports.taskStateEnum = void 0;
3
+ exports.idempotencyRecords = exports.outbox = exports.events = exports.leases = exports.taskAttempts = exports.tasks = exports.agentInstances = exports.agentDefinitions = exports.apiKeys = exports.principals = exports.workspaces = exports.leaseTypeEnum = exports.leaseStatusEnum = exports.agentStatusEnum = exports.workspaceStatusEnum = exports.taskStateEnum = void 0;
4
4
  const pg_core_1 = require("drizzle-orm/pg-core");
5
5
  exports.taskStateEnum = (0, pg_core_1.pgEnum)('task_state', [
6
6
  'draft', 'ready', 'leased', 'running', 'blocked',
@@ -19,6 +19,16 @@ exports.workspaces = (0, pg_core_1.pgTable)('workspaces', {
19
19
  createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).notNull().defaultNow(),
20
20
  updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }).notNull().defaultNow(),
21
21
  });
22
+ exports.principals = (0, pg_core_1.pgTable)('principals', {
23
+ id: (0, pg_core_1.uuid)('id').primaryKey(),
24
+ name: (0, pg_core_1.text)('name').notNull(),
25
+ createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).notNull().defaultNow(),
26
+ });
27
+ exports.apiKeys = (0, pg_core_1.pgTable)('api_keys', {
28
+ keyHash: (0, pg_core_1.text)('key_hash').primaryKey(),
29
+ principalId: (0, pg_core_1.uuid)('principal_id').notNull().references(() => exports.principals.id),
30
+ createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).notNull().defaultNow(),
31
+ });
22
32
  exports.agentDefinitions = (0, pg_core_1.pgTable)('agent_definitions', {
23
33
  id: (0, pg_core_1.uuid)('id').primaryKey(),
24
34
  name: (0, pg_core_1.text)('name').notNull(),
@@ -17,7 +17,7 @@ class AgentService {
17
17
  return workspace;
18
18
  }
19
19
  async registerAgent(params) {
20
- const workspace = await this.ensureDefaultWorkspace();
20
+ const workspace = await this.ensureDefaultWorkspace(); // Fallback logic or just rely on params
21
21
  // 1. Create or find agent definition
22
22
  let [definition] = await index_js_1.db.select().from(schema_js_1.agentDefinitions)
23
23
  .where((0, drizzle_orm_1.eq)(schema_js_1.agentDefinitions.name, params.name))
@@ -44,8 +44,8 @@ class AgentService {
44
44
  const [instance] = await index_js_1.db.insert(schema_js_1.agentInstances).values({
45
45
  id: (0, crypto_1.randomUUID)(),
46
46
  agentDefinitionId: definition.id,
47
- workspaceId: workspace.id,
48
- principalId: (0, crypto_1.randomUUID)(), // Mocking principal ID for Phase 0
47
+ workspaceId: params.workspaceId,
48
+ principalId: params.principalId,
49
49
  capabilitiesJson: params.capabilities,
50
50
  status: "connected",
51
51
  }).returning();
@@ -4,6 +4,7 @@ exports.taskService = exports.TaskService = void 0;
4
4
  const index_js_1 = require("../../infrastructure/db/index.js");
5
5
  const schema_js_1 = require("../../infrastructure/db/schema.js");
6
6
  const crypto_1 = require("crypto");
7
+ const drizzle_orm_1 = require("drizzle-orm");
7
8
  const event_service_js_1 = require("../event/event.service.js");
8
9
  class TaskService {
9
10
  async createTask(params) {
@@ -35,6 +36,74 @@ class TaskService {
35
36
  return task;
36
37
  });
37
38
  }
39
+ async submitImplementation(params) {
40
+ return await index_js_1.db.transaction(async (tx) => {
41
+ const [task] = await tx.select().from(schema_js_1.tasks).where((0, drizzle_orm_1.eq)(schema_js_1.tasks.id, params.taskId)).for('update');
42
+ if (!task)
43
+ throw new Error('Task not found');
44
+ if (task.nextFencingToken !== params.fencingToken) {
45
+ throw new Error('Invalid fencing token. You no longer hold the active lease.');
46
+ }
47
+ const [updatedTask] = await tx.update(schema_js_1.tasks).set({
48
+ state: 'awaiting_review',
49
+ activeExecutionLeaseId: null,
50
+ version: task.version + 1,
51
+ updatedAt: new Date()
52
+ }).where((0, drizzle_orm_1.eq)(schema_js_1.tasks.id, params.taskId)).returning();
53
+ if (task.activeExecutionLeaseId) {
54
+ await tx.update(schema_js_1.leases).set({
55
+ status: 'released',
56
+ releasedAt: new Date()
57
+ }).where((0, drizzle_orm_1.eq)(schema_js_1.leases.id, task.activeExecutionLeaseId));
58
+ }
59
+ await event_service_js_1.eventService.appendEvent(tx, {
60
+ workspaceId: task.workspaceId,
61
+ eventType: 'ImplementationSubmitted',
62
+ aggregateType: 'Task',
63
+ aggregateId: task.id,
64
+ aggregateVersion: updatedTask.version,
65
+ actorType: 'Agent',
66
+ actorId: params.agentInstanceId,
67
+ payload: { pullRequestUrl: params.pullRequestUrl }
68
+ });
69
+ return updatedTask;
70
+ });
71
+ }
72
+ async submitReview(params) {
73
+ return await index_js_1.db.transaction(async (tx) => {
74
+ const [task] = await tx.select().from(schema_js_1.tasks).where((0, drizzle_orm_1.eq)(schema_js_1.tasks.id, params.taskId)).for('update');
75
+ if (!task)
76
+ throw new Error('Task not found');
77
+ if (task.nextFencingToken !== params.fencingToken) {
78
+ throw new Error('Invalid fencing token. You no longer hold the active lease.');
79
+ }
80
+ const newState = params.status === 'approved' ? 'completed' : 'changes_requested';
81
+ const [updatedTask] = await tx.update(schema_js_1.tasks).set({
82
+ state: newState,
83
+ activeReviewLeaseId: null,
84
+ version: task.version + 1,
85
+ completedAt: params.status === 'approved' ? new Date() : null,
86
+ updatedAt: new Date()
87
+ }).where((0, drizzle_orm_1.eq)(schema_js_1.tasks.id, params.taskId)).returning();
88
+ if (task.activeReviewLeaseId) {
89
+ await tx.update(schema_js_1.leases).set({
90
+ status: 'released',
91
+ releasedAt: new Date()
92
+ }).where((0, drizzle_orm_1.eq)(schema_js_1.leases.id, task.activeReviewLeaseId));
93
+ }
94
+ await event_service_js_1.eventService.appendEvent(tx, {
95
+ workspaceId: task.workspaceId,
96
+ eventType: 'ReviewSubmitted',
97
+ aggregateType: 'Task',
98
+ aggregateId: task.id,
99
+ aggregateVersion: updatedTask.version,
100
+ actorType: 'Agent',
101
+ actorId: params.agentInstanceId,
102
+ payload: { status: params.status, feedback: params.feedback }
103
+ });
104
+ return updatedTask;
105
+ });
106
+ }
38
107
  }
39
108
  exports.TaskService = TaskService;
40
109
  exports.taskService = new TaskService();
@@ -4,6 +4,10 @@ exports.setupMcpTransport = setupMcpTransport;
4
4
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
5
  const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
6
6
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const crypto_1 = require("crypto");
8
+ const drizzle_orm_1 = require("drizzle-orm");
9
+ const index_js_2 = require("../../infrastructure/db/index.js");
10
+ const schema_js_1 = require("../../infrastructure/db/schema.js");
7
11
  const agent_service_js_1 = require("../../modules/identity/agent.service.js");
8
12
  const task_service_js_1 = require("../../modules/task/task.service.js");
9
13
  const lease_service_js_1 = require("../../modules/lease/lease.service.js");
@@ -11,6 +15,19 @@ async function setupMcpTransport(fastify) {
11
15
  // Store transports for the POST /mcp/message route
12
16
  const transports = new Map();
13
17
  fastify.get("/mcp/sse", async (request, reply) => {
18
+ const authHeader = request.headers.authorization;
19
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
20
+ reply.status(401).send({ error: "Missing or invalid Authorization header" });
21
+ return;
22
+ }
23
+ const token = authHeader.split(' ')[1];
24
+ const keyHash = (0, crypto_1.createHash)('sha256').update(token).digest('hex');
25
+ const [apiKeyRecord] = await index_js_2.db.select().from(schema_js_1.apiKeys).where((0, drizzle_orm_1.eq)(schema_js_1.apiKeys.keyHash, keyHash));
26
+ if (!apiKeyRecord) {
27
+ reply.status(401).send({ error: "Invalid API Key" });
28
+ return;
29
+ }
30
+ const principalId = apiKeyRecord.principalId;
14
31
  // Hijack the Fastify response so we can stream SSE headers manually
15
32
  reply.hijack();
16
33
  const transport = new sse_js_1.SSEServerTransport("/mcp/message", reply.raw);
@@ -67,6 +84,35 @@ async function setupMcpTransport(fastify) {
67
84
  },
68
85
  required: ["taskId", "workspaceId", "agentInstanceId", "leaseType"]
69
86
  }
87
+ },
88
+ {
89
+ name: "submit_task_implementation",
90
+ description: "Submit task implementation for review",
91
+ inputSchema: {
92
+ type: "object",
93
+ properties: {
94
+ taskId: { type: "string" },
95
+ agentInstanceId: { type: "string" },
96
+ fencingToken: { type: "number" },
97
+ pullRequestUrl: { type: "string" }
98
+ },
99
+ required: ["taskId", "agentInstanceId", "fencingToken"]
100
+ }
101
+ },
102
+ {
103
+ name: "submit_task_review",
104
+ description: "Submit a review (approve/reject) for an implemented task",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: {
108
+ taskId: { type: "string" },
109
+ agentInstanceId: { type: "string" },
110
+ fencingToken: { type: "number" },
111
+ status: { type: "string", enum: ["approved", "rejected"] },
112
+ feedback: { type: "string" }
113
+ },
114
+ required: ["taskId", "agentInstanceId", "fencingToken", "status"]
115
+ }
70
116
  }
71
117
  ]
72
118
  };
@@ -75,7 +121,9 @@ async function setupMcpTransport(fastify) {
75
121
  if (req.params.name === "register_agent") {
76
122
  try {
77
123
  const args = req.params.arguments;
78
- const instance = await agent_service_js_1.agentService.registerAgent({
124
+ const agentInstance = await agent_service_js_1.agentService.registerAgent({
125
+ workspaceId: args.workspaceId || (0, crypto_1.randomUUID)(), // Temporary fallback
126
+ principalId: principalId,
79
127
  name: args.name,
80
128
  version: args.version,
81
129
  capabilities: args.capabilities,
@@ -84,7 +132,7 @@ async function setupMcpTransport(fastify) {
84
132
  content: [
85
133
  {
86
134
  type: "text",
87
- text: `Agent registered successfully! Instance ID: ${instance.id}`
135
+ text: `Agent registered successfully! Instance ID: ${agentInstance.id}`
88
136
  }
89
137
  ]
90
138
  };
@@ -135,6 +183,47 @@ async function setupMcpTransport(fastify) {
135
183
  throw e;
136
184
  }
137
185
  }
186
+ else if (req.params.name === "submit_task_implementation") {
187
+ try {
188
+ const args = req.params.arguments;
189
+ const task = await task_service_js_1.taskService.submitImplementation({
190
+ taskId: args.taskId,
191
+ agentInstanceId: args.agentInstanceId,
192
+ fencingToken: args.fencingToken,
193
+ pullRequestUrl: args.pullRequestUrl
194
+ });
195
+ return {
196
+ content: [
197
+ { type: "text", text: `Implementation submitted successfully! Task state is now: ${task.state}` }
198
+ ]
199
+ };
200
+ }
201
+ catch (e) {
202
+ console.error("Error in submit_task_implementation:", e);
203
+ throw e;
204
+ }
205
+ }
206
+ else if (req.params.name === "submit_task_review") {
207
+ try {
208
+ const args = req.params.arguments;
209
+ const task = await task_service_js_1.taskService.submitReview({
210
+ taskId: args.taskId,
211
+ agentInstanceId: args.agentInstanceId,
212
+ fencingToken: args.fencingToken,
213
+ status: args.status,
214
+ feedback: args.feedback
215
+ });
216
+ return {
217
+ content: [
218
+ { type: "text", text: `Review submitted successfully! Task state is now: ${task.state}` }
219
+ ]
220
+ };
221
+ }
222
+ catch (e) {
223
+ console.error("Error in submit_task_review:", e);
224
+ throw e;
225
+ }
226
+ }
138
227
  throw new Error(`Tool not found: ${req.params.name}`);
139
228
  });
140
229
  await mcpServer.connect(transport);
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "mcp-agent-broker",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "main": "dist/app.js",
5
5
  "bin": {
6
- "mcp-agent-broker": "dist/app.js"
6
+ "mcp-agent-broker": "dist/bin.js"
7
7
  },
8
8
  "scripts": {
9
9
  "build": "tsc",
10
10
  "start": "node dist/app.js",
11
11
  "dev": "tsx watch src/app.ts",
12
- "test": "echo \"Error: no test specified\" && exit 1"
12
+ "test": "vitest run"
13
13
  },
14
14
  "keywords": [],
15
15
  "author": "",
@@ -30,6 +30,7 @@
30
30
  "@types/pg": "^8.20.0",
31
31
  "drizzle-kit": "^0.31.10",
32
32
  "tsx": "^4.23.1",
33
- "typescript": "^7.0.2"
33
+ "typescript": "^7.0.2",
34
+ "vitest": "^4.1.10"
34
35
  }
35
36
  }
@@ -0,0 +1,30 @@
1
+ import { db } from './src/infrastructure/db/index.js';
2
+ import { principals, apiKeys, workspaces } from './src/infrastructure/db/schema.js';
3
+ import { randomUUID, createHash } from 'crypto';
4
+
5
+ async function seed() {
6
+ const principalId = randomUUID();
7
+ const rawKey = 'test-api-key-123';
8
+ const keyHash = createHash('sha256').update(rawKey).digest('hex');
9
+
10
+ const workspaceId = '00000000-0000-0000-0000-000000000000';
11
+ await db.insert(workspaces).values({
12
+ id: workspaceId,
13
+ name: 'Test Workspace',
14
+ }).onConflictDoNothing();
15
+
16
+ await db.insert(principals).values({
17
+ id: principalId,
18
+ name: 'Test Service Account',
19
+ }).onConflictDoNothing();
20
+
21
+ await db.insert(apiKeys).values({
22
+ keyHash,
23
+ principalId,
24
+ });
25
+
26
+ console.log('Seeded principal:', principalId);
27
+ console.log('API Key:', rawKey);
28
+ process.exit(0);
29
+ }
30
+ seed().catch(console.error);
@@ -1,41 +0,0 @@
1
- services:
2
- postgres:
3
- image: postgres:16-alpine
4
- container_name: mab-postgres
5
- environment:
6
- POSTGRES_USER: mab_user
7
- POSTGRES_PASSWORD: mab_password
8
- POSTGRES_DB: mab_db
9
- ports:
10
- - "5433:5432"
11
- volumes:
12
- - postgres_data:/var/lib/postgresql/data
13
- restart: unless-stopped
14
-
15
- redis:
16
- image: redis:7-alpine
17
- container_name: mab-redis
18
- ports:
19
- - "6379:6379"
20
- volumes:
21
- - redis_data:/data
22
- restart: unless-stopped
23
-
24
- minio:
25
- image: minio/minio:latest
26
- container_name: mab-minio
27
- environment:
28
- MINIO_ROOT_USER: mab_admin
29
- MINIO_ROOT_PASSWORD: mab_password123
30
- command: server /data --console-address ":9001"
31
- ports:
32
- - "9000:9000"
33
- - "9001:9001"
34
- volumes:
35
- - minio_data:/data
36
- restart: unless-stopped
37
-
38
- volumes:
39
- postgres_data:
40
- redis_data:
41
- minio_data:
package/drizzle.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from "drizzle-kit";
2
-
3
- export default defineConfig({
4
- schema: "./src/infrastructure/db/schema.ts",
5
- out: "./src/infrastructure/db/migrations",
6
- dialect: "postgresql",
7
- dbCredentials: {
8
- url: process.env.DATABASE_URL || "postgres://mab_user:mab_password@127.0.0.1:5433/mab_db"
9
- }
10
- });
package/src/app.ts DELETED
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env node
2
- import Fastify from 'fastify';
3
- import { setupMcpTransport } from './transport/mcp/index.js';
4
- import { db } from './infrastructure/db/index.js';
5
- import { outboxRelay } from './workers/outbox-relay.js';
6
-
7
- const fastify = Fastify({
8
- logger: true
9
- });
10
-
11
- fastify.get('/health', async (request, reply) => {
12
- return { status: 'ok', service: 'mcp-agent-broker' };
13
- });
14
-
15
- // Start the server
16
- const start = async () => {
17
- try {
18
- await setupMcpTransport(fastify);
19
-
20
- // Start background workers
21
- outboxRelay.start();
22
-
23
- await fastify.listen({ port: 3000, host: '0.0.0.0' });
24
- fastify.log.info(`Server listening on ${fastify.server.address()}`);
25
- } catch (err) {
26
- fastify.log.error(err);
27
- process.exit(1);
28
- }
29
- };
30
-
31
- start();
@@ -1,10 +0,0 @@
1
- import { drizzle } from 'drizzle-orm/node-postgres';
2
- import { Pool } from 'pg';
3
- import * as schema from './schema';
4
-
5
- // Use a connection pool for the Fastify app
6
- const pool = new Pool({
7
- connectionString: process.env.DATABASE_URL || "postgres://mab_user:mab_password@127.0.0.1:5433/mab_db"
8
- });
9
-
10
- export const db = drizzle(pool, { schema });