/** * Stargue LinkedIn MCP Server — stdio transport. * * Pinned spec: MCP 2024-11-05. * * Tool wiring lives here; pure helpers (oauth, refresh-lock, kill-switch, tools) * are tested independently. The actual @modelcontextprotocol/sdk wiring requires * a runtime DB + LinkedIn credentials (gates 0.8 + 0.9), so this module exposes * a `createServer` factory that takes injected dependencies for testability. */ import { z } from "zod"; import { TOOL_NAMES, TOOL_SCHEMAS, type ToolName } from "./tools"; import { KillSwitch } from "./kill-switch"; import type { AdvisoryLock } from "./refresh-lock"; export const MCP_SERVER_NAME = "@stargue/mcp-linkedin"; export const MCP_SPEC_VERSION = "2024-11-05"; export interface ServerDeps { killSwitch: KillSwitch; advisoryLock: AdvisoryLock; // future: tokenStore, linkedInClient, dbPool — wired in Stage 3 live integration } export interface ToolCall { name: ToolName; args: unknown; } export interface ToolResult { ok: boolean; data?: T; error?: { code: string; message: string }; } /** * Validates a tool call against its registered schema. Returns parsed args or * a 400-style error result. */ export const validateToolCall = (call: ToolCall): { ok: true; args: unknown } | ToolResult => { const schema = TOOL_SCHEMAS[call.name as ToolName] as { input: z.ZodTypeAny } | undefined; if (!schema) { return { ok: false, error: { code: "UNKNOWN_TOOL", message: `Unknown tool: ${call.name}` } }; } const parse = schema.input.safeParse(call.args); if (!parse.success) { return { ok: false, error: { code: "INVALID_ARGS", message: parse.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "), }, }; } return { ok: true, args: parse.data }; }; export const listTools = (): ReadonlyArray<{ name: ToolName; description: string }> => TOOL_NAMES.map((name) => ({ name, description: TOOL_SCHEMAS[name].description, })); export const outletForAuthorUrn = (urn: string): "linkedin.member" | "linkedin.org" => { if (urn.startsWith("urn:li:person:")) return "linkedin.member"; if (urn.startsWith("urn:li:organization:")) return "linkedin.org"; throw new Error(`Cannot derive outlet from URN: ${urn}`); };