Initial meta MCP server scaffold
This commit is contained in:
130
src/server.ts
Normal file
130
src/server.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
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 type { MetaRequest, MetaResponse } from './types.js';
|
||||
|
||||
const server = new McpServer(
|
||||
{
|
||||
name: 'meta-mcp',
|
||||
version: '0.1.0',
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const ToolRequirementSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
inputSchema: z.record(z.unknown()),
|
||||
outputSchema: z.record(z.unknown()).optional(),
|
||||
notes: z.string().optional(),
|
||||
});
|
||||
|
||||
const MetaRequestSchema = z.object({
|
||||
requirements: z.array(ToolRequirementSchema).min(1),
|
||||
timeoutSeconds: z.number().int().positive().optional(),
|
||||
requester: z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
pubkey: z.string().optional(),
|
||||
sessionId: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
context: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
server.registerTool(
|
||||
'meta.request_tool',
|
||||
{
|
||||
description:
|
||||
'Request creation of a new MCP tool/server. Returns a job id and later the server pubkey when available.',
|
||||
inputSchema: MetaRequestSchema,
|
||||
},
|
||||
async (args) => {
|
||||
const parsed = MetaRequestSchema.parse(args);
|
||||
const job = createJob(parsed as MetaRequest);
|
||||
|
||||
// TODO: Hook into build/deploy pipeline
|
||||
updateJob(job.id, {
|
||||
status: 'building',
|
||||
message: 'Accepted. Building MCP server (pipeline stub).',
|
||||
});
|
||||
|
||||
const response: MetaResponse = {
|
||||
status: 'building',
|
||||
message: 'Accepted. Building MCP server (pipeline stub).',
|
||||
jobId: job.id,
|
||||
};
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'meta.job_status',
|
||||
{
|
||||
description: 'Check status of a previously requested MCP tool build.',
|
||||
inputSchema: z.object({ jobId: z.string() }),
|
||||
},
|
||||
async (args) => {
|
||||
const job = getJob(args.jobId);
|
||||
if (!job) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({ error: 'job_not_found', jobId: args.jobId }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(job.response, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'meta.jobs_recent',
|
||||
{
|
||||
description: 'List recent build jobs for monitoring.',
|
||||
inputSchema: z.object({ limit: z.number().int().positive().optional() }),
|
||||
},
|
||||
async (args) => {
|
||||
const jobs = listJobs().slice(0, args.limit ?? 10).map((job) => job.response);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(jobs, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Meta MCP server failed to start', err);
|
||||
process.exit(1);
|
||||
});
|
||||
41
src/storage.ts
Normal file
41
src/storage.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import type { MetaRequest, MetaResponse } from './types.js';
|
||||
|
||||
export type Job = {
|
||||
id: string;
|
||||
request: MetaRequest;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
response: MetaResponse;
|
||||
};
|
||||
|
||||
const jobs = new Map<string, Job>();
|
||||
|
||||
export function createJob(request: MetaRequest): Job {
|
||||
const id = randomUUID();
|
||||
const now = Date.now();
|
||||
const response: MetaResponse = {
|
||||
status: 'queued',
|
||||
message: 'Request received. Preparing build pipeline.',
|
||||
jobId: id,
|
||||
};
|
||||
const job: Job = { id, request, createdAt: now, updatedAt: now, response };
|
||||
jobs.set(id, job);
|
||||
return job;
|
||||
}
|
||||
|
||||
export function updateJob(id: string, patch: Partial<MetaResponse>): Job | null {
|
||||
const job = jobs.get(id);
|
||||
if (!job) return null;
|
||||
job.updatedAt = Date.now();
|
||||
job.response = { ...job.response, ...patch };
|
||||
return job;
|
||||
}
|
||||
|
||||
export function getJob(id: string): Job | null {
|
||||
return jobs.get(id) ?? null;
|
||||
}
|
||||
|
||||
export function listJobs(): Job[] {
|
||||
return [...jobs.values()].sort((a, b) => b.createdAt - a.createdAt);
|
||||
}
|
||||
27
src/types.ts
Normal file
27
src/types.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export type ToolRequirement = {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: Record<string, unknown>;
|
||||
outputSchema?: Record<string, unknown>;
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
export type MetaRequest = {
|
||||
requirements: ToolRequirement[];
|
||||
timeoutSeconds?: number;
|
||||
requester?: {
|
||||
name?: string;
|
||||
pubkey?: string;
|
||||
sessionId?: string;
|
||||
};
|
||||
context?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type MetaResponse = {
|
||||
status: 'queued' | 'building' | 'running' | 'failed';
|
||||
message: string;
|
||||
jobId: string;
|
||||
serverPubkey?: string;
|
||||
repoUrl?: string;
|
||||
logs?: string[];
|
||||
};
|
||||
Reference in New Issue
Block a user