diff --git a/src/pipeline.ts b/src/pipeline.ts index 4ea348e..15dd99e 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -67,7 +67,7 @@ async function createSystemdService(repoName: string, repoDir: string, seedPath: return serviceName; } -function appendNotification(payload: { serviceName: string; description: string }) { +function appendNotification(payload: { serviceName: string; description: string; pubkey?: string }) { const file = '/root/.meta-mcp/notifications.jsonl'; const dir = path.dirname(file); fs.mkdirSync(dir, { recursive: true }); @@ -201,7 +201,11 @@ export async function runPipeline(jobId: string, request: MetaRequest): Promise< const cvmiArgs = ['-y', 'cvmi', 'serve', '--', 'node', 'dist/server.js']; const child = spawn('npx', cvmiArgs, { cwd: repoDir, env, stdio: 'pipe' }); + const serviceName = toServiceName(repoName); + const desc = request.requirements[0]?.description ?? repoName; let pubkey: string | undefined; + let notified = false; + child.stdout.on('data', (d) => { const text = d.toString(); if (!pubkey) { @@ -213,6 +217,10 @@ export async function runPipeline(jobId: string, request: MetaRequest): Promise< message: `Server running for ${repoName}`, serverPubkey: pubkey, }); + if (!notified) { + appendNotification({ serviceName, description: desc, pubkey }); + notified = true; + } } } }); @@ -223,10 +231,11 @@ export async function runPipeline(jobId: string, request: MetaRequest): Promise< }); // create a systemd service to keep the MCP alive - const serviceName = await createSystemdService(repoName, repoDir, repoSeedPath); + await createSystemdService(repoName, repoDir, repoSeedPath); - const desc = request.requirements[0]?.description ?? repoName; - appendNotification({ serviceName, description: desc }); + if (!notified) { + appendNotification({ serviceName, description: desc }); + } return { status: 'running', diff --git a/src/server.ts b/src/server.ts index 97754ce..bb1c725 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,7 +1,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; -import { createJob, getJob, listJobs, updateJob } from './storage.js'; +import { createJob, findRecentJobBySignature, getJob, listJobs, updateJob } from './storage.js'; import type { MetaRequest, MetaResponse } from './types.js'; import { runPipeline } from './pipeline.js'; @@ -47,7 +47,21 @@ server.registerTool( }, async (args) => { const parsed = MetaRequestSchema.parse(args); - const job = createJob(parsed as MetaRequest); + const signature = JSON.stringify(parsed.requirements ?? []); + + const existing = findRecentJobBySignature(signature, 10 * 60 * 1000); + if (existing) { + return { + content: [ + { + type: 'text', + text: JSON.stringify(existing.response, null, 2), + }, + ], + }; + } + + const job = createJob(parsed as MetaRequest, signature); updateJob(job.id, { status: 'building', diff --git a/src/storage.ts b/src/storage.ts index 0ea0006..4ab804a 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -6,6 +6,7 @@ import type { MetaRequest, MetaResponse } from './types.js'; export type Job = { id: string; request: MetaRequest; + signature?: string; createdAt: number; updatedAt: number; response: MetaResponse; @@ -31,7 +32,7 @@ function hydrate() { hydrate(); -export function createJob(request: MetaRequest): Job { +export function createJob(request: MetaRequest, signature?: string): Job { const id = randomUUID(); const now = Date.now(); const response: MetaResponse = { @@ -39,7 +40,7 @@ export function createJob(request: MetaRequest): Job { message: 'Request received. Preparing build pipeline.', jobId: id, }; - const job: Job = { id, request, createdAt: now, updatedAt: now, response }; + const job: Job = { id, request, signature, createdAt: now, updatedAt: now, response }; jobs.set(id, job); persist(); return job; @@ -65,3 +66,13 @@ export function getJob(id: string): Job | null { export function listJobs(): Job[] { return [...jobs.values()].sort((a, b) => b.createdAt - a.createdAt); } + +export function findRecentJobBySignature(signature: string, maxAgeMs: number): Job | null { + const now = Date.now(); + for (const job of jobs.values()) { + if (job.signature === signature && now - job.createdAt <= maxAgeMs) { + return job; + } + } + return null; +}