feat(lib): bundle MCP servers from .common/lib into factory/lib/

Copies source (no node_modules, dist, .tasks, .wiki, __pycache__) for:
- projects-meta-mcp v2.25.0 (TypeScript/Node)
- wiki-graph v0.3.1 (TypeScript/Node)
- interns-mcp v0.3.3 (Python/FastMCP)

.gitignore: exclude lib build artefacts (node_modules, dist, .venv, __pycache__, *.pyc)
bootstrap.ps1: add MCP build step — npm install+build for TS servers, venv+pip for Python

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 13:17:12 +03:00
parent 9eeae13e72
commit ca669d96e1
116 changed files with 25331 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { readFileSync } from 'node:fs';
import { homedir, hostname } from 'node:os';
import { getPaths, loadAuth } from './lib/config.js';
import { resolveSourceProject } from './lib/domain-detector.js';
import { makeGiteaBackend } from './lib/gitea.js';
import { makeTasksTools } from './tools/tasks.js';
import { makeKnowledgeTools } from './tools/knowledge.js';
import { makeMetaTools } from './tools/meta.js';
import { makeWorkersTools } from './tools/workers.js';
import { makeOpsTools } from './tools/ops.js';
function getCwdArg(): string {
const i = process.argv.indexOf('--cwd');
if (i >= 0 && process.argv[i + 1]) return process.argv[i + 1];
return process.cwd();
}
// Resolves to package.json regardless of run context: dist/server.js → ../package.json,
// or src/server.ts (tests / ts-node) → ../package.json. Keeps the MCP server-info
// version field in sync with package.json without a hardcoded literal.
function readPackageVersion(): string {
const pkgUrl = new URL('../package.json', import.meta.url);
const raw = readFileSync(pkgUrl, 'utf8');
const pkg = JSON.parse(raw) as { version?: unknown };
if (typeof pkg.version !== 'string' || pkg.version.length === 0) {
throw new Error(`package.json at ${pkgUrl.pathname} missing string "version" field`);
}
return pkg.version;
}
async function main(): Promise<void> {
const paths = getPaths(homedir());
const cwd = getCwdArg();
// Auth + backend are optional for read-only operation. Write tools surface
// a clear error if these aren't available.
const auth = await loadAuth(paths.authFile).catch(() => null);
const backend = auth ? makeGiteaBackend({ baseUrl: auth.giteaUrl, token: auth.giteaToken }) : undefined;
const { sourceProject, fellBack } = await resolveSourceProject(cwd);
if (fellBack) {
console.error(
`[projects-meta] warning: cwd=${cwd} has no parseable git remote — ` +
`identity-footer will use bare basename "${sourceProject}" instead of ` +
`qualified <owner>/<repo>. Tasks/wiki created from here will not record their source repo.`,
);
}
const tools = [
...makeTasksTools({
cacheFile: paths.cacheFile,
backend,
giteaUser: auth?.giteaUser,
agendaTasksRepo: auth?.agendaTasksRepo,
agendaTasksOwner: auth?.agendaTasksOwner,
aggregateSkipOwners: auth?.giteaAggregateSkipOwners,
machine: hostname(),
sourceProject,
}),
...makeKnowledgeTools({
wikiRoot: paths.sharedWikiClone,
projectCwd: cwd,
cacheFile: paths.cacheFile,
backend,
giteaUser: auth?.giteaUser,
projectsWikiRepo: auth?.projectsWikiRepo,
machine: hostname(),
sourceProject,
}),
...makeMetaTools({ paths }),
...makeWorkersTools(),
...makeOpsTools({ cacheFile: paths.cacheFile }),
];
const byName = new Map<string, (typeof tools)[number]>();
for (const t of tools) {
if (byName.has(t.name)) throw new Error(`Duplicate tool name: ${t.name}`);
byName.set(t.name, t);
}
const server = new Server(
{ name: 'projects-meta-mcp', version: readPackageVersion() },
{ capabilities: { tools: {} } },
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: tools.map((t) => ({
name: t.name,
description: t.description,
inputSchema: t.inputSchema,
})),
}));
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const tool = byName.get(req.params.name);
if (!tool) {
return {
content: [{ type: 'text', text: `Unknown tool: ${req.params.name}` }],
isError: true,
};
}
return tool.handler(req.params.arguments ?? {});
});
const transport = new StdioServerTransport();
await server.connect(transport);
}
await main();