agent-ecosystem/mcp-server/src/index.ts

164 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
import { pathToFileURL } from 'url';
import { FastMCP } from 'fastmcp';
import { registerTools } from './tools';
const HTTP_TRANSPORT = 'httpStream';
const STDIO_TRANSPORT = 'stdio';
const DEFAULT_HTTP_HOST = '127.0.0.1';
const DEFAULT_HTTP_ENDPOINT = '/mcp';
const MCP_HTTP_IDENTITY_SERVICE = 'agent-teams-mcp-http';
const MCP_HTTP_IDENTITY_SERVICE_ENV = 'AGENT_TEAMS_MCP_HTTP_IDENTITY_SERVICE';
const MCP_HTTP_CLAUDE_DIR_HASH_ENV = 'AGENT_TEAMS_MCP_HTTP_CLAUDE_DIR_HASH';
const MCP_HTTP_LAUNCH_SPEC_HASH_ENV = 'AGENT_TEAMS_MCP_HTTP_LAUNCH_SPEC_HASH';
const MCP_HTTP_OWNER_INSTANCE_ID_ENV = 'AGENT_TEAMS_MCP_HTTP_OWNER_INSTANCE_ID';
export type AgentTeamsMcpStartOptions =
| {
transportType: typeof STDIO_TRANSPORT;
}
| {
transportType: typeof HTTP_TRANSPORT;
httpStream: {
host: string;
port: number;
endpoint: `/${string}`;
};
};
export interface AgentTeamsMcpHttpHealthIdentity {
schemaVersion: 1;
service: typeof MCP_HTTP_IDENTITY_SERVICE;
transport: typeof HTTP_TRANSPORT;
host: string;
port: number;
endpoint: `/${string}`;
claudeDirHash: string;
launchSpecHash: string;
ownerInstanceId: string;
}
export function createServer(input: { healthIdentity?: AgentTeamsMcpHttpHealthIdentity | null } = {}) {
const server = new FastMCP({
name: 'agent-teams-mcp',
version: '1.0.0',
...(input.healthIdentity
? {
health: {
enabled: true,
path: '/health',
status: 200,
message: JSON.stringify(input.healthIdentity),
},
}
: {}),
});
registerTools(server);
return server;
}
function getArgValue(argv: string[], name: string): string | null {
const directPrefix = `${name}=`;
for (let index = 2; index < argv.length; index += 1) {
const value = argv[index];
if (value === name) {
return argv[index + 1] ?? null;
}
if (value.startsWith(directPrefix)) {
return value.slice(directPrefix.length);
}
}
return null;
}
function normalizeEndpoint(value: string | null | undefined): `/${string}` {
const trimmed = value?.trim();
if (!trimmed) {
return DEFAULT_HTTP_ENDPOINT;
}
return (trimmed.startsWith('/') ? trimmed : `/${trimmed}`) as `/${string}`;
}
function parsePort(value: string | null | undefined): number {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
throw new Error(`Invalid agent-teams MCP HTTP port: ${value ?? '<empty>'}`);
}
return parsed;
}
function readIdentityValue(env: NodeJS.ProcessEnv, name: string): string | null {
const value = env[name]?.trim();
return value && value.length > 0 ? value : null;
}
export function buildHttpHealthIdentity(
options: AgentTeamsMcpStartOptions,
env: NodeJS.ProcessEnv = process.env
): AgentTeamsMcpHttpHealthIdentity | null {
if (options.transportType !== HTTP_TRANSPORT) {
return null;
}
const service = readIdentityValue(env, MCP_HTTP_IDENTITY_SERVICE_ENV);
const claudeDirHash = readIdentityValue(env, MCP_HTTP_CLAUDE_DIR_HASH_ENV);
const launchSpecHash = readIdentityValue(env, MCP_HTTP_LAUNCH_SPEC_HASH_ENV);
const ownerInstanceId = readIdentityValue(env, MCP_HTTP_OWNER_INSTANCE_ID_ENV);
if (
service !== MCP_HTTP_IDENTITY_SERVICE ||
!claudeDirHash ||
!launchSpecHash ||
!ownerInstanceId
) {
return null;
}
return {
schemaVersion: 1,
service: MCP_HTTP_IDENTITY_SERVICE,
transport: HTTP_TRANSPORT,
host: options.httpStream.host,
port: options.httpStream.port,
endpoint: options.httpStream.endpoint,
claudeDirHash,
launchSpecHash,
ownerInstanceId,
};
}
export function resolveStartOptions(
argv: string[] = process.argv,
env: NodeJS.ProcessEnv = process.env
): AgentTeamsMcpStartOptions {
const transport =
getArgValue(argv, '--transport') ??
getArgValue(argv, '--transportType') ??
env.AGENT_TEAMS_MCP_TRANSPORT ??
STDIO_TRANSPORT;
if (transport !== HTTP_TRANSPORT) {
return { transportType: STDIO_TRANSPORT };
}
return {
transportType: HTTP_TRANSPORT,
httpStream: {
host:
getArgValue(argv, '--host')?.trim() ??
env.AGENT_TEAMS_MCP_HTTP_HOST?.trim() ??
DEFAULT_HTTP_HOST,
port: parsePort(getArgValue(argv, '--port') ?? env.AGENT_TEAMS_MCP_HTTP_PORT),
endpoint: normalizeEndpoint(getArgValue(argv, '--endpoint') ?? env.AGENT_TEAMS_MCP_HTTP_ENDPOINT),
},
};
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const startOptions = resolveStartOptions();
const server = createServer({ healthIdentity: buildHttpHealthIdentity(startOptions) });
void server.start(startOptions);
}