Initial meta MCP server scaffold

This commit is contained in:
root
2026-03-12 09:39:39 +00:00
commit b11edee7c4
8 changed files with 1715 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules/
dist/
.env
.DS_Store
*.log

100
README.md Normal file
View File

@@ -0,0 +1,100 @@
# Meta MCP (ContextVM)
Meta MCP is a ContextVM server that accepts **requirements** for new MCP tools and returns a job id. It is intended to be used by other AIs that only have access to a single "meta" tool. The meta MCP then builds, deploys, and announces a new MCP server via Nostr (ContextVM).
This repo contains the **server-side** MCP for the meta tool. It currently implements the request + job tracking surface; the build pipeline is stubbed and will be wired to the ContextVM/CVMI deployment flow next.
---
## Goals
- Accept requirements for new tools
- Create a repo/code for the new MCP server
- Build + run the MCP server on this VPS
- Announce the server on Nostr (ContextVM)
- Return the **server pubkey** back to the requester
---
## Tool API
### `meta.request_tool`
Request a new MCP tool/server.
**Input**
```json
{
"requirements": [
{
"name": "get_current_weather_by_city",
"description": "Returns current weather for a city",
"inputSchema": {"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}
}
],
"timeoutSeconds": 120,
"requester": {"name":"weather-bot","pubkey":"npub1..."},
"context": {"notes":"Need fast response"}
}
```
**Output**
```json
{
"status": "building",
"message": "Accepted. Building MCP server (pipeline stub).",
"jobId": "<uuid>"
}
```
### `meta.job_status`
Check a previous job.
**Input**
```json
{ "jobId": "<uuid>" }
```
### `meta.jobs_recent`
List recent jobs.
**Input**
```json
{ "limit": 10 }
```
---
## Development
```bash
npm install
npm run dev
```
---
## Deployment (ContextVM/CVMI)
This server runs via stdio. Use **cvmi serve** to expose it over Nostr.
Example:
```bash
cvmi serve -- node dist/server.js
```
You can set `CVMI_SERVE_*` env vars to configure relays, encryption, and keys.
---
## Next Steps
- Wire this server to a build/deploy pipeline
- Add persistent storage for job state
- Auto-create repos + scaffolds for new MCPs
- Auto-start new MCP servers and announce via CVMI
---
## License
MIT

1377
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "meta-mcp",
"version": "0.1.0",
"description": "Meta MCP ContextVM: accepts requirements to create and announce new MCP servers over Nostr",
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "node dist/server.js",
"dev": "ts-node --esm src/server.ts",
"lint": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.27.1",
"uuid": "^11.1.0",
"zod": "^3.25.0"
},
"devDependencies": {
"ts-node": "^10.9.2",
"typescript": "^5.6.3"
}
}

130
src/server.ts Normal file
View 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
View 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
View 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[];
};

14
tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"]
}