@indexnetwork/protocol 1.26.8 → 1.26.9-rc.237.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.server.d.ts","sourceRoot":"/","sources":["mcp/mcp.server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,SAAS,EAAkB,MAAM,8BAA8B,CAAC;AAGzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAErF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sCAAsC,CAAC;AAiBrE;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAI9D;AA8ED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAqBlF;AAKD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAkBxE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CAErE;AAMD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC;CACrF;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,cAAc;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,EAAE,EAClE,gBAAgB,MAAM,GAAG,IAAI,GAAG,SAAS,KACxC,MAAM,EAOR,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B,GACrC,SAAS,mBAAmB,EAC5B,gBAAgB,MAAM,GAAG,IAAI,GAAG,SAAS,KACxC,IAyBF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAiBjD,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAyB1E;AAwCD,eAAO,MAAM,gBAAgB,QAsDrB,CAAC;AAET,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,EACd,YAAY,EAAE,eAAe,EAC7B,iBAAiB,EAAE,iBAAiB,GACnC,SAAS,CA4NX"}
1
+ {"version":3,"file":"mcp.server.d.ts","sourceRoot":"/","sources":["mcp/mcp.server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,SAAS,EAAkB,MAAM,8BAA8B,CAAC;AAGzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAErF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sCAAsC,CAAC;AAiBrE;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAI9D;AA8ED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAqBlF;AAKD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAkBxE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CAErE;AAMD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC;CACrF;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,cAAc;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,EAAE,EAClE,gBAAgB,MAAM,GAAG,IAAI,GAAG,SAAS,KACxC,MAAM,EAOR,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B,GACrC,SAAS,mBAAmB,EAC5B,gBAAgB,MAAM,GAAG,IAAI,GAAG,SAAS,KACxC,IAyBF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAiBjD,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAyB1E;AAwCD,eAAO,MAAM,gBAAgB,QAsDrB,CAAC;AAET,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,EACd,YAAY,EAAE,eAAe,EAC7B,iBAAiB,EAAE,iBAAiB,GACnC,SAAS,CAoQX"}
@@ -407,6 +407,45 @@ export function createMcpServer(deps, authResolver, scopedDepsFactory) {
407
407
  // Resolve authenticated identity (userId + optional agentId + optional network scope + optional surface)
408
408
  const { userId, agentId, isSessionAuth, networkScopeId, clientSurface } = await authResolver.resolveIdentity(httpReq);
409
409
  reportUserId = userId;
410
+ // Per-principal MCP throttle. Runs BEFORE any DB work so a throttled
411
+ // call short-circuits cheaply. The /mcp transport bypasses the
412
+ // controller-level RateLimit guard, so this is the only volume cap on
413
+ // tool calls — it stops an over-eager agent from cascading itself into
414
+ // provider rate limits.
415
+ if (deps.mcpRateLimiter) {
416
+ // Throttling is best-effort: never let a limiter failure (or a host
417
+ // implementation that throws instead of failing open) break tool
418
+ // dispatch. Treat any error as "allowed".
419
+ let decision = null;
420
+ try {
421
+ decision = await deps.mcpRateLimiter({
422
+ userId,
423
+ ...(agentId ? { agentId } : {}),
424
+ toolName,
425
+ });
426
+ }
427
+ catch (rlErr) {
428
+ logger.warn(`MCP rate limiter threw for "${toolName}" — failing open`, {
429
+ error: rlErr instanceof Error ? rlErr.message : String(rlErr),
430
+ });
431
+ }
432
+ if (decision && !decision.allowed) {
433
+ const retryAfterSec = decision.retryAfterSec ?? 60;
434
+ return {
435
+ content: [{
436
+ type: 'text',
437
+ text: JSON.stringify({
438
+ error: 'Rate limit exceeded',
439
+ message: `Too many ${toolName} calls in a short period. Wait ${retryAfterSec}s before retrying, ` +
440
+ `and avoid re-issuing the same request — if a discovery run is in progress, poll ` +
441
+ `get_discovery_run instead of calling discover_opportunities again.`,
442
+ retryAfterSec,
443
+ }),
444
+ }],
445
+ isError: true,
446
+ };
447
+ }
448
+ }
410
449
  // Resolve chat context for the user (mark as MCP — no interactive UI available)
411
450
  const context = await resolveChatContext({ database: deps.database, userId });
412
451
  reportContext = context;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.server.js","sourceRoot":"/","sources":["mcp/mcp.server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP Server Factory — creates an McpServer instance with all protocol tools\n * registered from the existing tool registry. Each tool invocation resolves\n * auth from the HTTP request, builds a ResolvedToolContext, and delegates\n * to the raw tool handler.\n */\n\nimport { z } from 'zod';\nimport { McpServer, fromJsonSchema } from '@modelcontextprotocol/server';\nimport type { ServerContext, JsonSchemaType } from '@modelcontextprotocol/server';\n\nimport type { McpAuthResolver } from '../shared/interfaces/auth.interface.js';\nimport type { ToolDeps, ResolvedToolContext } from '../shared/agent/tool.helpers.js';\nimport { resolveChatContext } from '../shared/agent/tool.helpers.js';\nimport type { Question } from '../shared/schemas/question.schema.js';\nimport { QuestionSchema } from '../shared/schemas/question.schema.js';\nimport { dispatchElicitations } from './elicitation.dispatcher.js';\nimport { createToolRegistry } from '../shared/agent/tool.registry.js';\nimport { ToolRuntimeError, invokeToolRuntime, toolRuntimeErrorToResult } from '../shared/agent/tool.runtime.js';\nimport type { TraceEmitter } from '../shared/observability/request-context.js';\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('McpServer');\n\nfunction isExpectedMcpAuthError(message: string): boolean {\n return message.includes('Authentication required') ||\n message.includes('Invalid API key') ||\n message.includes('Invalid or expired access token') ||\n message.includes('JWT payload missing user ID');\n}\n\n/**\n * Runtime/auth failures are converted into structured MCP `isError` tool\n * results for the caller. Reporting them as application exceptions produces\n * Sentry noise for expected client failures and policy-enforced timeouts.\n */\nexport function shouldReportMcpToolError(err: unknown): boolean {\n if (err instanceof ToolRuntimeError) return false;\n const message = err instanceof Error ? err.message : String(err);\n return !isExpectedMcpAuthError(message);\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// ZOD 3 → JSON SCHEMA CONVERSION\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Minimal Zod-to-JSON-Schema conversion for MCP tool registration.\n * Converts Zod 3.x schemas to plain JSON Schema objects that can be\n * wrapped with `fromJsonSchema()` for MCP SDK compatibility.\n */\nfunction zodToJsonSchema(schema: z.ZodType): Record<string, unknown> {\n if (schema instanceof z.ZodObject) {\n const shape = schema.shape;\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n for (const [key, value] of Object.entries(shape)) {\n const zodValue = value as z.ZodType;\n properties[key] = zodToJsonSchema(zodValue);\n if (!(zodValue instanceof z.ZodOptional) && !(zodValue instanceof z.ZodDefault)) {\n required.push(key);\n }\n }\n return { type: 'object', properties, ...(required.length ? { required } : {}) };\n }\n if (schema instanceof z.ZodString) {\n const result: Record<string, unknown> = { type: 'string' };\n // Detect .url(), .email(), .uuid() etc. via Zod's internal checks array\n const checks = (schema as z.ZodString & { _def: { checks: Array<{ kind: string }> } })._def?.checks;\n if (checks) {\n for (const check of checks) {\n if (check.kind === 'url') result.format = 'uri';\n else if (check.kind === 'email') result.format = 'email';\n else if (check.kind === 'uuid') result.format = 'uuid';\n else if (check.kind === 'datetime') result.format = 'date-time';\n }\n }\n return result;\n }\n if (schema instanceof z.ZodNumber) {\n const checks = (schema as z.ZodNumber & { _def: { checks: Array<{ kind: string; value?: number }> } })._def?.checks;\n const result: Record<string, unknown> = { type: 'number' };\n if (checks) {\n for (const check of checks) {\n if (check.kind === 'int') result.type = 'integer';\n else if (check.kind === 'min') result.minimum = check.value;\n else if (check.kind === 'max') result.maximum = check.value;\n }\n }\n return result;\n }\n if (schema instanceof z.ZodBoolean) return { type: 'boolean' };\n if (schema instanceof z.ZodArray) {\n return { type: 'array', items: zodToJsonSchema((schema as z.ZodArray<z.ZodType>).element) };\n }\n if (schema instanceof z.ZodOptional) {\n return zodToJsonSchema((schema as z.ZodOptional<z.ZodType>).unwrap());\n }\n if (schema instanceof z.ZodDefault) {\n return zodToJsonSchema((schema as z.ZodDefault<z.ZodType>).removeDefault());\n }\n if (schema instanceof z.ZodEnum) {\n return { type: 'string', enum: (schema as z.ZodEnum<[string, ...string[]]>).options };\n }\n if (schema instanceof z.ZodNullable) {\n const inner = zodToJsonSchema((schema as z.ZodNullable<z.ZodType>).unwrap());\n return { ...inner, nullable: true };\n }\n if (schema instanceof z.ZodRecord) {\n return { type: 'object', additionalProperties: true };\n }\n return { type: 'object' };\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// RESULT POST-PROCESSING\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Strips internal `_`-prefixed keys from `data` and promotes `isError`\n * from the inner `success: false` signal to the MCP envelope level.\n * Fail-open: if JSON parsing throws, returns the original text with isError: false.\n */\nexport function sanitizeMcpResult(text: string): { text: string; isError: boolean } {\n try {\n const parsed = JSON.parse(text);\n if (\n parsed &&\n typeof parsed === 'object' &&\n parsed.data &&\n typeof parsed.data === 'object' &&\n !Array.isArray(parsed.data)\n ) {\n for (const key of Object.keys(parsed.data)) {\n if (key.startsWith('_') || key === 'debugSteps') {\n delete parsed.data[key];\n }\n }\n }\n const isError = parsed?.success === false;\n return { text: JSON.stringify(parsed), isError };\n } catch {\n return { text, isError: false };\n }\n}\n\n/** Spec cap on the number of decision questions surfaced per turn. */\nconst MAX_DECISION_QUESTIONS = 3;\n\n/**\n * Extracts decision questions from a parsed tool-result text, if present.\n * Validates each entry against `QuestionSchema` and drops malformed items;\n * caps the array at `MAX_DECISION_QUESTIONS` (defense-in-depth — Slice 2's\n * generator already caps at 3, but we don't trust the cast here).\n *\n * Returns null when the text isn't JSON, has no `data.questions`, or\n * contains zero valid questions after validation.\n */\nexport function extractDecisionQuestions(text: string): Question[] | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return null;\n }\n\n const rawQs = (parsed as { data?: { questions?: unknown } } | null)?.data?.questions;\n if (!Array.isArray(rawQs) || rawQs.length === 0) return null;\n\n const valid: Question[] = [];\n for (const raw of rawQs) {\n const result = QuestionSchema.safeParse(raw);\n if (result.success) valid.push(result.data);\n if (valid.length === MAX_DECISION_QUESTIONS) break;\n }\n return valid.length > 0 ? valid : null;\n}\n\n/**\n * Renders the JSON-envelope text block appended to the tool result content\n * when decision questions are present. The leading sentinel string lets the\n * LLM client recognize and surface the questions in prose for clients\n * without elicitation support.\n */\nexport function renderQuestionsEnvelope(questions: Question[]): string {\n return `Decision questions (structured): ${JSON.stringify({ questions })}`;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// MCP SERVER FACTORY\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Factory for creating per-request scoped database instances.\n * Injected from the controller/handler layer to keep the protocol layer\n * free of direct adapter imports.\n */\nexport interface ScopedDepsFactory {\n /** Creates scoped userDb and systemDb for the given user and index scope. */\n create(userId: string, indexScope: string[]): Pick<ToolDeps, 'userDb' | 'systemDb'>;\n}\n\n/**\n * Computes the index scope passed to the per-request scoped DB factory. When\n * `networkScopeId` is non-null, the agent is bound to a single network and\n * may only reach that network plus the user's personal index. Otherwise the\n * full set of the user's network memberships is returned.\n */\nexport const computeAgentIndexScope = (\n userNetworks: { networkId: string; isPersonal?: boolean | null }[],\n networkScopeId: string | null | undefined,\n): string[] => {\n if (!networkScopeId) {\n return userNetworks.map((m) => m.networkId);\n }\n return userNetworks\n .filter((m) => m.networkId === networkScopeId || m.isPersonal === true)\n .map((m) => m.networkId);\n};\n\n/**\n * Promotes a network-scoped agent's bound network into the resolved tool\n * context as the implicit chat scope. Every tool that branches on\n * `context.networkId` (read_networks, read_intents, read_user_profiles,\n * opportunity tools, etc.) then enforces scope automatically — without this\n * step the DB-level `indexScope` clamp guards cross-user data but tools that\n * shape their response off `context.networkId` (notably `read_networks`'\n * `publicNetworks` branch) would still leak the global view.\n *\n * No-op when there is no scope, or when an explicit chat scope is already\n * set (a user-driven index-scoped chat must keep precedence over the agent\n * binding — which would be a strict subset anyway, since the API key cannot\n * reach beyond its bound network).\n */\nexport const applyNetworkScopeToContext = (\n context: ResolvedToolContext,\n networkScopeId: string | null | undefined,\n): void => {\n if (!networkScopeId) return;\n if (context.networkId) return;\n\n context.networkId = networkScopeId;\n // Clamp indexScope to [boundNetwork, personalIndex] BEFORE the membership\n // check below. If the bound network is not in userNetworks (defensive case),\n // the filter still produces a safe scope (personal index only) rather than\n // leaving the unclamped scope set by resolveChatContext.\n context.indexScope = context.userNetworks\n .filter((m) => m.networkId === networkScopeId || m.isPersonal === true)\n .map((m) => m.networkId);\n\n const bound = context.userNetworks.find((m) => m.networkId === networkScopeId);\n if (!bound) return;\n\n context.indexName = bound.networkTitle;\n context.scopedIndex = {\n id: bound.networkId,\n title: bound.networkTitle,\n prompt: bound.indexPrompt ?? null,\n };\n const isOwner = bound.permissions?.includes('owner') ?? false;\n context.scopedMembershipRole = isOwner ? 'owner' : 'member';\n context.isOwner = isOwner;\n};\n\n/**\n * Tools allowed during onboarding — everything else is gated until\n * complete_onboarding is called. Includes the agent-gate-exempt tools\n * (register_agent, read_docs, scrape_url) because they are informational /\n * registration primitives needed at every lifecycle stage.\n */\nexport const ONBOARDING_ALLOWED: ReadonlySet<string> = new Set([\n 'register_agent',\n 'read_docs',\n 'scrape_url',\n 'record_onboarding_privacy_consent',\n 'preview_user_profile',\n 'get_profile_run',\n 'cancel_profile_run',\n 'confirm_user_profile',\n 'create_user_profile',\n 'complete_onboarding',\n 'import_gmail_contacts',\n 'read_networks',\n 'create_network_membership',\n 'create_intent',\n 'discover_opportunities',\n 'read_user_profiles',\n]);\n\n/**\n * Builds the onboarding gate message for MCP callers. Condensed from the\n * chat orchestrator's 8-step flow (chat.prompt.ts buildOnboarding) into a\n * 7-step tool-error guide suited for non-interactive MCP clients.\n */\nexport function buildMcpOnboardingMessage(ctx: ResolvedToolContext): string {\n const nameStep = ctx.hasName\n ? `1. Greet the user and confirm their name (\"You're ${ctx.userName}, right?\").`\n : `1. Ask the user for their name and a short self-description.`;\n\n const communityStep = ctx.networkId\n ? `5. (Skipped — user is already in \"${ctx.indexName ?? 'their community'}\".)`\n : `5. Call read_networks() and let the user pick communities to join via create_network_membership(networkId=...).`;\n\n const allowedList = Array.from(ONBOARDING_ALLOWED).join(', ');\n\n return (\n `This user has not completed onboarding. You must guide them through setup before they can use other tools. ` +\n `Only the following tools are available until onboarding is complete: ` +\n `${allowedList}.\\n\\n` +\n `Onboarding flow:\\n` +\n `${nameStep}\\n` +\n `2. Ask whether the user allows use of event/EdgeOS profile data, then call record_onboarding_privacy_consent(edgeosImportGranted=...).\\n` +\n `3. Ask separately whether the user allows public internet/profile lookup, then call record_onboarding_privacy_consent(publicProfileLookupGranted=...).\\n` +\n `4. Call preview_user_profile(...) using only allowed inputs; do not run public lookup unless consent was granted. If it returns profileRunId, poll get_profile_run(profileRunId=...) until status is succeeded, then use its result as the draft.\\n` +\n `5. Present the profile draft and ask \"Does that look right?\" On approval/correction, call confirm_user_profile(...).\\n` +\n `${communityStep}\\n` +\n `6. Ask what the user is looking for and call create_intent(description=\"...\").\\n` +\n `7. Call complete_onboarding() to finish setup. Gmail/contact import and discovery are optional after onboarding, never mandatory.`\n );\n}\n\n/**\n * Creates an MCP server with all protocol tools registered.\n * Tools resolve auth per-request via the HTTP request available in ServerContext.\n *\n * @param deps - Shared tool dependencies (graphs, database, embedder, etc.)\n * @param authResolver - Resolves authenticated identity from the HTTP request\n * @param scopedDepsFactory - Factory for creating per-request scoped databases\n * @returns A configured McpServer ready to be connected to a transport\n */\nfunction createMcpTraceEmitter(toolName: string, ctx: ServerContext): TraceEmitter | undefined {\n const token = ctx.mcpReq._meta?.progressToken;\n if (typeof token !== 'string' && typeof token !== 'number') return undefined;\n\n let progress = 0;\n return (event) => {\n progress += 1;\n const message = (() => {\n if (event.type === 'graph_start') return `${toolName}: ${event.name} started`;\n if (event.type === 'graph_end') return `${toolName}: ${event.name} finished${event.durationMs != null ? ` in ${event.durationMs}ms` : ''}`;\n if (event.type === 'agent_start') return `${toolName}: ${event.name} agent started`;\n if (event.type === 'agent_end') return `${toolName}: ${event.name} agent finished${event.durationMs != null ? ` in ${event.durationMs}ms` : ''}`;\n if (event.type === 'opportunity_draft_ready') return `${toolName}: opportunity draft ready`;\n return `${toolName}: progress`;\n })();\n\n const notification: Parameters<ServerContext['mcpReq']['notify']>[0] = {\n method: 'notifications/progress',\n params: { progressToken: token, progress, message },\n };\n void ctx.mcpReq.notify(notification).catch((err) => {\n logger.debug('Failed to send MCP progress notification', {\n toolName,\n error: err instanceof Error ? err.message : String(err),\n });\n });\n };\n}\n\nexport const MCP_INSTRUCTIONS = `\nIndex Network is a private, intent-driven discovery protocol. You help users find the right people and help the right people find them, via Index Network MCP tools.\n\n# Voice\nCalm, direct, analytical, concise. Preferred vocabulary: opportunity, overlap, signal, pattern, emerging, relevant, adjacency.\n\n# Banned vocabulary\nNEVER use \"search\" in any form. Use \"looking up\" for indexed data, \"find\" / \"look for\" for discovery, \"check\" for verification, \"discover\" for exploration. Banned: leverage, unlock, optimize, scale, disrupt, revolutionary, AI-powered, maximize value, act fast, networking, match.\n\n# Entity model\n- User — has one Profile, many Memberships, many Intents.\n- Profile — identity (bio, skills, interests, location).\n- Index — community with title, prompt (purpose), join policy. Has Members.\n- Membership — User↔Index junction. \\`isPersonal: true\\` marks the user's personal index (contacts).\n- Intent — what a user is looking for (signal). Description, summary, embedding.\n- IntentIndex — Intent↔Index junction (auto-assigned).\n- Opportunity — discovered connection between users. Roles, status, reasoning.\n\n# Output rules\n- NEVER expose internal IDs, UUIDs, field names, or tool names — EXCEPT when an ID is actionable for the user (e.g. a \\`conversationId\\` they need to open a chat). Surface such IDs verbatim when the tool returns them.\n- NEVER use internal vocabulary — say \"signal\" not \"intent\", \"community\" not \"index\".\n- NEVER dump raw JSON. Synthesize in natural language.\n- Surface top 1–3 relevant points unless asked for the full list.\n- Prefer first names; use full names only to disambiguate.\n- Translate statuses: draft/latent → \"draft\", pending → \"sent\", accepted → \"connected\".\n- NEVER fabricate data. If you don't have it, call the appropriate tool.\n\n# Tool guidance\nEach tool's description contains its own usage rules (when to call, when NOT to call, required prerequisites, post-call follow-ups). Read the description of every tool you call — that is where the per-tool workflow patterns live.\n\n# Authentication\nPass your API key in the \\`x-api-key\\` request header (not \\`Authorization: Bearer\\`).\n\n# Opportunity lifecycle\nOpportunities move through: draft → pending → accepted (or rejected).\n\n- **draft** (you created it, not yet sent): offer to send it; confirm before calling update_opportunity with pending.\n- **pending, you sent it**: waiting for the other side — nothing to do.\n- **pending, you received it**: the other person is waiting for your response. Surface it to the user and ask if they want to start a chat. Only call update_opportunity with accepted after explicit user confirmation.\n- **accepted**: both sides are connected — a direct conversation exists. Surface the conversationId to the user if available.\n\nNever accept a received opportunity without explicit user approval in the current conversation.\n\n# Decision questions after discovery\n\nAfter \\`discover_opportunities\\`, the tool result may include a second text block starting with \\`Decision questions (structured): ...\\`. This means the discovery engine ran negotiations but needs human input to sharpen the next turn — e.g. clarify timing, role, stage, or location.\n\n**When this block is present:**\n1. Parse the \\`questions\\` array from the JSON after the sentinel.\n2. Each question has \\`title\\` (decision domain, ≤12 chars), \\`prompt\\` (ends in \\`?\\`), \\`options\\` (2–4 items, each with \\`label\\` and \\`description\\`), and \\`multiSelect\\`. The safest option is labeled \\`... (Recommended)\\`.\n3. Present each question in natural language: ask the \\`prompt\\`, list options as \\`**{label}** — {description}\\`. Never expose the JSON or technical field names.\n4. Wait for the user's answer, then fold it into the next \\`discover_opportunities(searchQuery=...)\\` call.\n\n**Elicitation-capable clients** (those that declared \\`elicitation\\` support in \\`initialize\\`): the server dispatches \\`elicitation/create\\` requests directly — answers are written back to the chat session automatically. You will not see the envelope as a follow-up task in that case.\n`.trim();\n\nexport function createMcpServer(\n deps: ToolDeps,\n authResolver: McpAuthResolver,\n scopedDepsFactory: ScopedDepsFactory,\n): McpServer {\n // Tools exempt from the agent-registration gate — available before registration is complete.\n const AGENT_GATE_EXEMPT = new Set(['register_agent', 'read_docs', 'scrape_url']);\n\n const server = new McpServer(\n { name: 'index-network', version: '1.0.0' },\n { instructions: MCP_INSTRUCTIONS },\n );\n\n const registry = createToolRegistry(deps);\n\n for (const [toolName, toolDef] of registry) {\n // Convert Zod 3 schema to JSON Schema, then wrap with fromJsonSchema\n // for MCP SDK's StandardSchemaWithJSON compatibility\n const jsonSchema = zodToJsonSchema(toolDef.schema) as JsonSchemaType;\n const mcpSchema = fromJsonSchema(jsonSchema);\n\n server.registerTool(\n toolName,\n {\n description: toolDef.description,\n inputSchema: mcpSchema,\n },\n async (args: unknown, ctx: ServerContext) => {\n let reportDeps = deps;\n let reportUserId: string | undefined;\n let reportContext: ResolvedToolContext | undefined;\n\n try {\n // Extract the original HTTP request from the MCP server context\n const httpReq = ctx.http?.req;\n if (!httpReq) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'No HTTP request available in MCP context' }) }],\n isError: true,\n };\n }\n\n // Resolve authenticated identity (userId + optional agentId + optional network scope + optional surface)\n const { userId, agentId, isSessionAuth, networkScopeId, clientSurface } = await authResolver.resolveIdentity(httpReq);\n reportUserId = userId;\n\n // Resolve chat context for the user (mark as MCP — no interactive UI available)\n const context = await resolveChatContext({ database: deps.database, userId });\n reportContext = context;\n context.isMcp = true;\n if (agentId) {\n context.agentId = agentId;\n }\n if (clientSurface) {\n context.clientSurface = clientSurface;\n }\n\n // Network-scoped agents inherit their bound network as the implicit chat\n // scope. Every tool that branches on `context.networkId` then enforces\n // the same boundary the DB-level `indexScope` clamp enforces below —\n // most importantly `read_networks`, which would otherwise return the\n // global `publicNetworks` catalog for unscoped contexts.\n applyNetworkScopeToContext(context, networkScopeId);\n\n // Gate: API-key callers (background agents) must register before using most tools.\n // OAuth/JWT session callers (human MCP clients such as Claude Code) are exempt —\n // their identity is already established via the auth flow and they have no agent entity.\n if (!isSessionAuth && !context.agentId && !AGENT_GATE_EXEMPT.has(toolName)) {\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Agent not registered',\n message:\n 'You must register as an agent before using Index tools. ' +\n 'Call register_agent with your agent name to establish an identity. ' +\n 'The tools register_agent, read_docs, and scrape_url are available without registration.',\n }),\n }],\n isError: true,\n };\n }\n\n // Gate: non-onboarded users can only use onboarding-related tools.\n // Mirrors the chat orchestrator's ONBOARDING MODE — the MCP client must\n // walk the user through profile creation, Gmail connect, intent capture,\n // and complete_onboarding() before full tool access is granted.\n if (context.isOnboarding && !ONBOARDING_ALLOWED.has(toolName)) {\n const onboardingSteps = buildMcpOnboardingMessage(context);\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Onboarding required',\n message: onboardingSteps,\n }),\n }],\n isError: true,\n };\n }\n\n // Build per-request scoped databases via injected factory.\n // Network-scoped agents are clamped to their bound network plus the user's\n // personal index — they cannot reach other networks even when the user is\n // a member of them. The personal-index reachability is preserved so the\n // agent can still manage its owner's profile and contacts.\n // context.indexScope is now the single source of truth: set by\n // resolveChatContext (full set) and narrowed by applyNetworkScopeToContext.\n const scopedDbs = scopedDepsFactory.create(userId, context.indexScope);\n\n // Override deps with per-request scoped databases\n const requestDeps: ToolDeps = { ...deps, ...scopedDbs };\n reportDeps = requestDeps;\n\n // Re-create registry with per-request deps for scoped database access\n const requestRegistry = createToolRegistry(requestDeps);\n const requestTool = requestRegistry.get(toolName);\n\n if (!requestTool) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Tool \"${toolName}\" not found` }) }],\n isError: true,\n };\n }\n\n // Validate input against the original Zod schema\n const parseResult = (toolDef.schema as z.ZodType).safeParse(args);\n if (!parseResult.success) {\n const issues = parseResult.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: false, error: `Invalid input: ${issues}` }) }],\n isError: true,\n };\n }\n const validatedArgs = parseResult.data;\n\n // Execute the tool handler through the shared runtime so MCP calls have\n // consistent timeout, cancellation, progress, and requestContext plumbing.\n const result = await invokeToolRuntime({\n toolName,\n tool: requestTool,\n context,\n query: validatedArgs,\n signal: ctx.mcpReq.signal,\n traceEmitter: createMcpTraceEmitter(toolName, ctx),\n });\n\n const { text: sanitizedText, isError: toolIsError } = sanitizeMcpResult(result);\n\n // Slice 5: decision questions post-processing for discover_opportunities only.\n if (toolName === \"discover_opportunities\" && !toolIsError) {\n const questions = extractDecisionQuestions(sanitizedText);\n if (questions) {\n const envelopeBlock = {\n type: \"text\" as const,\n text: renderQuestionsEnvelope(questions),\n };\n\n const supportsElicitation =\n !!server.server.getClientCapabilities()?.elicitation;\n\n // Capture into a local const so TS preserves the narrowing\n // inside the callback below. Optional chains don't survive\n // across closure boundaries under strict mode.\n const elicitInput = ctx.mcpReq?.elicitInput;\n\n if (supportsElicitation && elicitInput) {\n // Sequential — never parallel (day-one rule). We await the loop\n // before returning the tool result so test harnesses can observe\n // the dispatched calls deterministically.\n await dispatchElicitations({\n userId,\n questions,\n elicitInput: (params) => elicitInput(params),\n chatMessageWriter: deps.chatMessageWriter,\n });\n }\n\n return {\n content: [\n { type: \"text\" as const, text: sanitizedText },\n envelopeBlock,\n ],\n ...(toolIsError ? { isError: true } : {}),\n };\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: sanitizedText }],\n ...(toolIsError ? { isError: true } : {}),\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(`MCP tool \"${toolName}\" failed`, { error: message });\n if (shouldReportMcpToolError(err)) {\n reportDeps.reportToolError?.(err, {\n subsystem: 'mcp',\n operation: 'mcp.tool',\n toolName,\n userId: reportUserId,\n tags: {\n transport: 'mcp',\n toolName,\n },\n context: {\n agentId: reportContext?.agentId,\n networkId: reportContext?.networkId,\n indexScope: reportContext?.indexScope,\n },\n });\n }\n const runtimeResult = toolRuntimeErrorToResult(err);\n return {\n content: [{ type: 'text' as const, text: runtimeResult ?? JSON.stringify({ error: message }) }],\n isError: true,\n };\n }\n },\n );\n }\n\n logger.verbose(`MCP server created with ${registry.size} tools`);\n return server;\n}\n"]}
1
+ {"version":3,"file":"mcp.server.js","sourceRoot":"/","sources":["mcp/mcp.server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP Server Factory — creates an McpServer instance with all protocol tools\n * registered from the existing tool registry. Each tool invocation resolves\n * auth from the HTTP request, builds a ResolvedToolContext, and delegates\n * to the raw tool handler.\n */\n\nimport { z } from 'zod';\nimport { McpServer, fromJsonSchema } from '@modelcontextprotocol/server';\nimport type { ServerContext, JsonSchemaType } from '@modelcontextprotocol/server';\n\nimport type { McpAuthResolver } from '../shared/interfaces/auth.interface.js';\nimport type { ToolDeps, ResolvedToolContext } from '../shared/agent/tool.helpers.js';\nimport { resolveChatContext } from '../shared/agent/tool.helpers.js';\nimport type { Question } from '../shared/schemas/question.schema.js';\nimport { QuestionSchema } from '../shared/schemas/question.schema.js';\nimport { dispatchElicitations } from './elicitation.dispatcher.js';\nimport { createToolRegistry } from '../shared/agent/tool.registry.js';\nimport { ToolRuntimeError, invokeToolRuntime, toolRuntimeErrorToResult } from '../shared/agent/tool.runtime.js';\nimport type { TraceEmitter } from '../shared/observability/request-context.js';\nimport { protocolLogger } from '../shared/observability/protocol.logger.js';\n\nconst logger = protocolLogger('McpServer');\n\nfunction isExpectedMcpAuthError(message: string): boolean {\n return message.includes('Authentication required') ||\n message.includes('Invalid API key') ||\n message.includes('Invalid or expired access token') ||\n message.includes('JWT payload missing user ID');\n}\n\n/**\n * Runtime/auth failures are converted into structured MCP `isError` tool\n * results for the caller. Reporting them as application exceptions produces\n * Sentry noise for expected client failures and policy-enforced timeouts.\n */\nexport function shouldReportMcpToolError(err: unknown): boolean {\n if (err instanceof ToolRuntimeError) return false;\n const message = err instanceof Error ? err.message : String(err);\n return !isExpectedMcpAuthError(message);\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// ZOD 3 → JSON SCHEMA CONVERSION\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Minimal Zod-to-JSON-Schema conversion for MCP tool registration.\n * Converts Zod 3.x schemas to plain JSON Schema objects that can be\n * wrapped with `fromJsonSchema()` for MCP SDK compatibility.\n */\nfunction zodToJsonSchema(schema: z.ZodType): Record<string, unknown> {\n if (schema instanceof z.ZodObject) {\n const shape = schema.shape;\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n for (const [key, value] of Object.entries(shape)) {\n const zodValue = value as z.ZodType;\n properties[key] = zodToJsonSchema(zodValue);\n if (!(zodValue instanceof z.ZodOptional) && !(zodValue instanceof z.ZodDefault)) {\n required.push(key);\n }\n }\n return { type: 'object', properties, ...(required.length ? { required } : {}) };\n }\n if (schema instanceof z.ZodString) {\n const result: Record<string, unknown> = { type: 'string' };\n // Detect .url(), .email(), .uuid() etc. via Zod's internal checks array\n const checks = (schema as z.ZodString & { _def: { checks: Array<{ kind: string }> } })._def?.checks;\n if (checks) {\n for (const check of checks) {\n if (check.kind === 'url') result.format = 'uri';\n else if (check.kind === 'email') result.format = 'email';\n else if (check.kind === 'uuid') result.format = 'uuid';\n else if (check.kind === 'datetime') result.format = 'date-time';\n }\n }\n return result;\n }\n if (schema instanceof z.ZodNumber) {\n const checks = (schema as z.ZodNumber & { _def: { checks: Array<{ kind: string; value?: number }> } })._def?.checks;\n const result: Record<string, unknown> = { type: 'number' };\n if (checks) {\n for (const check of checks) {\n if (check.kind === 'int') result.type = 'integer';\n else if (check.kind === 'min') result.minimum = check.value;\n else if (check.kind === 'max') result.maximum = check.value;\n }\n }\n return result;\n }\n if (schema instanceof z.ZodBoolean) return { type: 'boolean' };\n if (schema instanceof z.ZodArray) {\n return { type: 'array', items: zodToJsonSchema((schema as z.ZodArray<z.ZodType>).element) };\n }\n if (schema instanceof z.ZodOptional) {\n return zodToJsonSchema((schema as z.ZodOptional<z.ZodType>).unwrap());\n }\n if (schema instanceof z.ZodDefault) {\n return zodToJsonSchema((schema as z.ZodDefault<z.ZodType>).removeDefault());\n }\n if (schema instanceof z.ZodEnum) {\n return { type: 'string', enum: (schema as z.ZodEnum<[string, ...string[]]>).options };\n }\n if (schema instanceof z.ZodNullable) {\n const inner = zodToJsonSchema((schema as z.ZodNullable<z.ZodType>).unwrap());\n return { ...inner, nullable: true };\n }\n if (schema instanceof z.ZodRecord) {\n return { type: 'object', additionalProperties: true };\n }\n return { type: 'object' };\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// RESULT POST-PROCESSING\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Strips internal `_`-prefixed keys from `data` and promotes `isError`\n * from the inner `success: false` signal to the MCP envelope level.\n * Fail-open: if JSON parsing throws, returns the original text with isError: false.\n */\nexport function sanitizeMcpResult(text: string): { text: string; isError: boolean } {\n try {\n const parsed = JSON.parse(text);\n if (\n parsed &&\n typeof parsed === 'object' &&\n parsed.data &&\n typeof parsed.data === 'object' &&\n !Array.isArray(parsed.data)\n ) {\n for (const key of Object.keys(parsed.data)) {\n if (key.startsWith('_') || key === 'debugSteps') {\n delete parsed.data[key];\n }\n }\n }\n const isError = parsed?.success === false;\n return { text: JSON.stringify(parsed), isError };\n } catch {\n return { text, isError: false };\n }\n}\n\n/** Spec cap on the number of decision questions surfaced per turn. */\nconst MAX_DECISION_QUESTIONS = 3;\n\n/**\n * Extracts decision questions from a parsed tool-result text, if present.\n * Validates each entry against `QuestionSchema` and drops malformed items;\n * caps the array at `MAX_DECISION_QUESTIONS` (defense-in-depth — Slice 2's\n * generator already caps at 3, but we don't trust the cast here).\n *\n * Returns null when the text isn't JSON, has no `data.questions`, or\n * contains zero valid questions after validation.\n */\nexport function extractDecisionQuestions(text: string): Question[] | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return null;\n }\n\n const rawQs = (parsed as { data?: { questions?: unknown } } | null)?.data?.questions;\n if (!Array.isArray(rawQs) || rawQs.length === 0) return null;\n\n const valid: Question[] = [];\n for (const raw of rawQs) {\n const result = QuestionSchema.safeParse(raw);\n if (result.success) valid.push(result.data);\n if (valid.length === MAX_DECISION_QUESTIONS) break;\n }\n return valid.length > 0 ? valid : null;\n}\n\n/**\n * Renders the JSON-envelope text block appended to the tool result content\n * when decision questions are present. The leading sentinel string lets the\n * LLM client recognize and surface the questions in prose for clients\n * without elicitation support.\n */\nexport function renderQuestionsEnvelope(questions: Question[]): string {\n return `Decision questions (structured): ${JSON.stringify({ questions })}`;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// MCP SERVER FACTORY\n// ═══════════════════════════════════════════════════════════════════════════════\n\n/**\n * Factory for creating per-request scoped database instances.\n * Injected from the controller/handler layer to keep the protocol layer\n * free of direct adapter imports.\n */\nexport interface ScopedDepsFactory {\n /** Creates scoped userDb and systemDb for the given user and index scope. */\n create(userId: string, indexScope: string[]): Pick<ToolDeps, 'userDb' | 'systemDb'>;\n}\n\n/**\n * Computes the index scope passed to the per-request scoped DB factory. When\n * `networkScopeId` is non-null, the agent is bound to a single network and\n * may only reach that network plus the user's personal index. Otherwise the\n * full set of the user's network memberships is returned.\n */\nexport const computeAgentIndexScope = (\n userNetworks: { networkId: string; isPersonal?: boolean | null }[],\n networkScopeId: string | null | undefined,\n): string[] => {\n if (!networkScopeId) {\n return userNetworks.map((m) => m.networkId);\n }\n return userNetworks\n .filter((m) => m.networkId === networkScopeId || m.isPersonal === true)\n .map((m) => m.networkId);\n};\n\n/**\n * Promotes a network-scoped agent's bound network into the resolved tool\n * context as the implicit chat scope. Every tool that branches on\n * `context.networkId` (read_networks, read_intents, read_user_profiles,\n * opportunity tools, etc.) then enforces scope automatically — without this\n * step the DB-level `indexScope` clamp guards cross-user data but tools that\n * shape their response off `context.networkId` (notably `read_networks`'\n * `publicNetworks` branch) would still leak the global view.\n *\n * No-op when there is no scope, or when an explicit chat scope is already\n * set (a user-driven index-scoped chat must keep precedence over the agent\n * binding — which would be a strict subset anyway, since the API key cannot\n * reach beyond its bound network).\n */\nexport const applyNetworkScopeToContext = (\n context: ResolvedToolContext,\n networkScopeId: string | null | undefined,\n): void => {\n if (!networkScopeId) return;\n if (context.networkId) return;\n\n context.networkId = networkScopeId;\n // Clamp indexScope to [boundNetwork, personalIndex] BEFORE the membership\n // check below. If the bound network is not in userNetworks (defensive case),\n // the filter still produces a safe scope (personal index only) rather than\n // leaving the unclamped scope set by resolveChatContext.\n context.indexScope = context.userNetworks\n .filter((m) => m.networkId === networkScopeId || m.isPersonal === true)\n .map((m) => m.networkId);\n\n const bound = context.userNetworks.find((m) => m.networkId === networkScopeId);\n if (!bound) return;\n\n context.indexName = bound.networkTitle;\n context.scopedIndex = {\n id: bound.networkId,\n title: bound.networkTitle,\n prompt: bound.indexPrompt ?? null,\n };\n const isOwner = bound.permissions?.includes('owner') ?? false;\n context.scopedMembershipRole = isOwner ? 'owner' : 'member';\n context.isOwner = isOwner;\n};\n\n/**\n * Tools allowed during onboarding — everything else is gated until\n * complete_onboarding is called. Includes the agent-gate-exempt tools\n * (register_agent, read_docs, scrape_url) because they are informational /\n * registration primitives needed at every lifecycle stage.\n */\nexport const ONBOARDING_ALLOWED: ReadonlySet<string> = new Set([\n 'register_agent',\n 'read_docs',\n 'scrape_url',\n 'record_onboarding_privacy_consent',\n 'preview_user_profile',\n 'get_profile_run',\n 'cancel_profile_run',\n 'confirm_user_profile',\n 'create_user_profile',\n 'complete_onboarding',\n 'import_gmail_contacts',\n 'read_networks',\n 'create_network_membership',\n 'create_intent',\n 'discover_opportunities',\n 'read_user_profiles',\n]);\n\n/**\n * Builds the onboarding gate message for MCP callers. Condensed from the\n * chat orchestrator's 8-step flow (chat.prompt.ts buildOnboarding) into a\n * 7-step tool-error guide suited for non-interactive MCP clients.\n */\nexport function buildMcpOnboardingMessage(ctx: ResolvedToolContext): string {\n const nameStep = ctx.hasName\n ? `1. Greet the user and confirm their name (\"You're ${ctx.userName}, right?\").`\n : `1. Ask the user for their name and a short self-description.`;\n\n const communityStep = ctx.networkId\n ? `5. (Skipped — user is already in \"${ctx.indexName ?? 'their community'}\".)`\n : `5. Call read_networks() and let the user pick communities to join via create_network_membership(networkId=...).`;\n\n const allowedList = Array.from(ONBOARDING_ALLOWED).join(', ');\n\n return (\n `This user has not completed onboarding. You must guide them through setup before they can use other tools. ` +\n `Only the following tools are available until onboarding is complete: ` +\n `${allowedList}.\\n\\n` +\n `Onboarding flow:\\n` +\n `${nameStep}\\n` +\n `2. Ask whether the user allows use of event/EdgeOS profile data, then call record_onboarding_privacy_consent(edgeosImportGranted=...).\\n` +\n `3. Ask separately whether the user allows public internet/profile lookup, then call record_onboarding_privacy_consent(publicProfileLookupGranted=...).\\n` +\n `4. Call preview_user_profile(...) using only allowed inputs; do not run public lookup unless consent was granted. If it returns profileRunId, poll get_profile_run(profileRunId=...) until status is succeeded, then use its result as the draft.\\n` +\n `5. Present the profile draft and ask \"Does that look right?\" On approval/correction, call confirm_user_profile(...).\\n` +\n `${communityStep}\\n` +\n `6. Ask what the user is looking for and call create_intent(description=\"...\").\\n` +\n `7. Call complete_onboarding() to finish setup. Gmail/contact import and discovery are optional after onboarding, never mandatory.`\n );\n}\n\n/**\n * Creates an MCP server with all protocol tools registered.\n * Tools resolve auth per-request via the HTTP request available in ServerContext.\n *\n * @param deps - Shared tool dependencies (graphs, database, embedder, etc.)\n * @param authResolver - Resolves authenticated identity from the HTTP request\n * @param scopedDepsFactory - Factory for creating per-request scoped databases\n * @returns A configured McpServer ready to be connected to a transport\n */\nfunction createMcpTraceEmitter(toolName: string, ctx: ServerContext): TraceEmitter | undefined {\n const token = ctx.mcpReq._meta?.progressToken;\n if (typeof token !== 'string' && typeof token !== 'number') return undefined;\n\n let progress = 0;\n return (event) => {\n progress += 1;\n const message = (() => {\n if (event.type === 'graph_start') return `${toolName}: ${event.name} started`;\n if (event.type === 'graph_end') return `${toolName}: ${event.name} finished${event.durationMs != null ? ` in ${event.durationMs}ms` : ''}`;\n if (event.type === 'agent_start') return `${toolName}: ${event.name} agent started`;\n if (event.type === 'agent_end') return `${toolName}: ${event.name} agent finished${event.durationMs != null ? ` in ${event.durationMs}ms` : ''}`;\n if (event.type === 'opportunity_draft_ready') return `${toolName}: opportunity draft ready`;\n return `${toolName}: progress`;\n })();\n\n const notification: Parameters<ServerContext['mcpReq']['notify']>[0] = {\n method: 'notifications/progress',\n params: { progressToken: token, progress, message },\n };\n void ctx.mcpReq.notify(notification).catch((err) => {\n logger.debug('Failed to send MCP progress notification', {\n toolName,\n error: err instanceof Error ? err.message : String(err),\n });\n });\n };\n}\n\nexport const MCP_INSTRUCTIONS = `\nIndex Network is a private, intent-driven discovery protocol. You help users find the right people and help the right people find them, via Index Network MCP tools.\n\n# Voice\nCalm, direct, analytical, concise. Preferred vocabulary: opportunity, overlap, signal, pattern, emerging, relevant, adjacency.\n\n# Banned vocabulary\nNEVER use \"search\" in any form. Use \"looking up\" for indexed data, \"find\" / \"look for\" for discovery, \"check\" for verification, \"discover\" for exploration. Banned: leverage, unlock, optimize, scale, disrupt, revolutionary, AI-powered, maximize value, act fast, networking, match.\n\n# Entity model\n- User — has one Profile, many Memberships, many Intents.\n- Profile — identity (bio, skills, interests, location).\n- Index — community with title, prompt (purpose), join policy. Has Members.\n- Membership — User↔Index junction. \\`isPersonal: true\\` marks the user's personal index (contacts).\n- Intent — what a user is looking for (signal). Description, summary, embedding.\n- IntentIndex — Intent↔Index junction (auto-assigned).\n- Opportunity — discovered connection between users. Roles, status, reasoning.\n\n# Output rules\n- NEVER expose internal IDs, UUIDs, field names, or tool names — EXCEPT when an ID is actionable for the user (e.g. a \\`conversationId\\` they need to open a chat). Surface such IDs verbatim when the tool returns them.\n- NEVER use internal vocabulary — say \"signal\" not \"intent\", \"community\" not \"index\".\n- NEVER dump raw JSON. Synthesize in natural language.\n- Surface top 1–3 relevant points unless asked for the full list.\n- Prefer first names; use full names only to disambiguate.\n- Translate statuses: draft/latent → \"draft\", pending → \"sent\", accepted → \"connected\".\n- NEVER fabricate data. If you don't have it, call the appropriate tool.\n\n# Tool guidance\nEach tool's description contains its own usage rules (when to call, when NOT to call, required prerequisites, post-call follow-ups). Read the description of every tool you call — that is where the per-tool workflow patterns live.\n\n# Authentication\nPass your API key in the \\`x-api-key\\` request header (not \\`Authorization: Bearer\\`).\n\n# Opportunity lifecycle\nOpportunities move through: draft → pending → accepted (or rejected).\n\n- **draft** (you created it, not yet sent): offer to send it; confirm before calling update_opportunity with pending.\n- **pending, you sent it**: waiting for the other side — nothing to do.\n- **pending, you received it**: the other person is waiting for your response. Surface it to the user and ask if they want to start a chat. Only call update_opportunity with accepted after explicit user confirmation.\n- **accepted**: both sides are connected — a direct conversation exists. Surface the conversationId to the user if available.\n\nNever accept a received opportunity without explicit user approval in the current conversation.\n\n# Decision questions after discovery\n\nAfter \\`discover_opportunities\\`, the tool result may include a second text block starting with \\`Decision questions (structured): ...\\`. This means the discovery engine ran negotiations but needs human input to sharpen the next turn — e.g. clarify timing, role, stage, or location.\n\n**When this block is present:**\n1. Parse the \\`questions\\` array from the JSON after the sentinel.\n2. Each question has \\`title\\` (decision domain, ≤12 chars), \\`prompt\\` (ends in \\`?\\`), \\`options\\` (2–4 items, each with \\`label\\` and \\`description\\`), and \\`multiSelect\\`. The safest option is labeled \\`... (Recommended)\\`.\n3. Present each question in natural language: ask the \\`prompt\\`, list options as \\`**{label}** — {description}\\`. Never expose the JSON or technical field names.\n4. Wait for the user's answer, then fold it into the next \\`discover_opportunities(searchQuery=...)\\` call.\n\n**Elicitation-capable clients** (those that declared \\`elicitation\\` support in \\`initialize\\`): the server dispatches \\`elicitation/create\\` requests directly — answers are written back to the chat session automatically. You will not see the envelope as a follow-up task in that case.\n`.trim();\n\nexport function createMcpServer(\n deps: ToolDeps,\n authResolver: McpAuthResolver,\n scopedDepsFactory: ScopedDepsFactory,\n): McpServer {\n // Tools exempt from the agent-registration gate — available before registration is complete.\n const AGENT_GATE_EXEMPT = new Set(['register_agent', 'read_docs', 'scrape_url']);\n\n const server = new McpServer(\n { name: 'index-network', version: '1.0.0' },\n { instructions: MCP_INSTRUCTIONS },\n );\n\n const registry = createToolRegistry(deps);\n\n for (const [toolName, toolDef] of registry) {\n // Convert Zod 3 schema to JSON Schema, then wrap with fromJsonSchema\n // for MCP SDK's StandardSchemaWithJSON compatibility\n const jsonSchema = zodToJsonSchema(toolDef.schema) as JsonSchemaType;\n const mcpSchema = fromJsonSchema(jsonSchema);\n\n server.registerTool(\n toolName,\n {\n description: toolDef.description,\n inputSchema: mcpSchema,\n },\n async (args: unknown, ctx: ServerContext) => {\n let reportDeps = deps;\n let reportUserId: string | undefined;\n let reportContext: ResolvedToolContext | undefined;\n\n try {\n // Extract the original HTTP request from the MCP server context\n const httpReq = ctx.http?.req;\n if (!httpReq) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'No HTTP request available in MCP context' }) }],\n isError: true,\n };\n }\n\n // Resolve authenticated identity (userId + optional agentId + optional network scope + optional surface)\n const { userId, agentId, isSessionAuth, networkScopeId, clientSurface } = await authResolver.resolveIdentity(httpReq);\n reportUserId = userId;\n\n // Per-principal MCP throttle. Runs BEFORE any DB work so a throttled\n // call short-circuits cheaply. The /mcp transport bypasses the\n // controller-level RateLimit guard, so this is the only volume cap on\n // tool calls — it stops an over-eager agent from cascading itself into\n // provider rate limits.\n if (deps.mcpRateLimiter) {\n // Throttling is best-effort: never let a limiter failure (or a host\n // implementation that throws instead of failing open) break tool\n // dispatch. Treat any error as \"allowed\".\n let decision: Awaited<ReturnType<NonNullable<typeof deps.mcpRateLimiter>>> | null = null;\n try {\n decision = await deps.mcpRateLimiter({\n userId,\n ...(agentId ? { agentId } : {}),\n toolName,\n });\n } catch (rlErr) {\n logger.warn(`MCP rate limiter threw for \"${toolName}\" — failing open`, {\n error: rlErr instanceof Error ? rlErr.message : String(rlErr),\n });\n }\n if (decision && !decision.allowed) {\n const retryAfterSec = decision.retryAfterSec ?? 60;\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Rate limit exceeded',\n message:\n `Too many ${toolName} calls in a short period. Wait ${retryAfterSec}s before retrying, ` +\n `and avoid re-issuing the same request — if a discovery run is in progress, poll ` +\n `get_discovery_run instead of calling discover_opportunities again.`,\n retryAfterSec,\n }),\n }],\n isError: true,\n };\n }\n }\n\n // Resolve chat context for the user (mark as MCP — no interactive UI available)\n const context = await resolveChatContext({ database: deps.database, userId });\n reportContext = context;\n context.isMcp = true;\n if (agentId) {\n context.agentId = agentId;\n }\n if (clientSurface) {\n context.clientSurface = clientSurface;\n }\n\n // Network-scoped agents inherit their bound network as the implicit chat\n // scope. Every tool that branches on `context.networkId` then enforces\n // the same boundary the DB-level `indexScope` clamp enforces below —\n // most importantly `read_networks`, which would otherwise return the\n // global `publicNetworks` catalog for unscoped contexts.\n applyNetworkScopeToContext(context, networkScopeId);\n\n // Gate: API-key callers (background agents) must register before using most tools.\n // OAuth/JWT session callers (human MCP clients such as Claude Code) are exempt —\n // their identity is already established via the auth flow and they have no agent entity.\n if (!isSessionAuth && !context.agentId && !AGENT_GATE_EXEMPT.has(toolName)) {\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Agent not registered',\n message:\n 'You must register as an agent before using Index tools. ' +\n 'Call register_agent with your agent name to establish an identity. ' +\n 'The tools register_agent, read_docs, and scrape_url are available without registration.',\n }),\n }],\n isError: true,\n };\n }\n\n // Gate: non-onboarded users can only use onboarding-related tools.\n // Mirrors the chat orchestrator's ONBOARDING MODE — the MCP client must\n // walk the user through profile creation, Gmail connect, intent capture,\n // and complete_onboarding() before full tool access is granted.\n if (context.isOnboarding && !ONBOARDING_ALLOWED.has(toolName)) {\n const onboardingSteps = buildMcpOnboardingMessage(context);\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Onboarding required',\n message: onboardingSteps,\n }),\n }],\n isError: true,\n };\n }\n\n // Build per-request scoped databases via injected factory.\n // Network-scoped agents are clamped to their bound network plus the user's\n // personal index — they cannot reach other networks even when the user is\n // a member of them. The personal-index reachability is preserved so the\n // agent can still manage its owner's profile and contacts.\n // context.indexScope is now the single source of truth: set by\n // resolveChatContext (full set) and narrowed by applyNetworkScopeToContext.\n const scopedDbs = scopedDepsFactory.create(userId, context.indexScope);\n\n // Override deps with per-request scoped databases\n const requestDeps: ToolDeps = { ...deps, ...scopedDbs };\n reportDeps = requestDeps;\n\n // Re-create registry with per-request deps for scoped database access\n const requestRegistry = createToolRegistry(requestDeps);\n const requestTool = requestRegistry.get(toolName);\n\n if (!requestTool) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Tool \"${toolName}\" not found` }) }],\n isError: true,\n };\n }\n\n // Validate input against the original Zod schema\n const parseResult = (toolDef.schema as z.ZodType).safeParse(args);\n if (!parseResult.success) {\n const issues = parseResult.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ success: false, error: `Invalid input: ${issues}` }) }],\n isError: true,\n };\n }\n const validatedArgs = parseResult.data;\n\n // Execute the tool handler through the shared runtime so MCP calls have\n // consistent timeout, cancellation, progress, and requestContext plumbing.\n const result = await invokeToolRuntime({\n toolName,\n tool: requestTool,\n context,\n query: validatedArgs,\n signal: ctx.mcpReq.signal,\n traceEmitter: createMcpTraceEmitter(toolName, ctx),\n });\n\n const { text: sanitizedText, isError: toolIsError } = sanitizeMcpResult(result);\n\n // Slice 5: decision questions post-processing for discover_opportunities only.\n if (toolName === \"discover_opportunities\" && !toolIsError) {\n const questions = extractDecisionQuestions(sanitizedText);\n if (questions) {\n const envelopeBlock = {\n type: \"text\" as const,\n text: renderQuestionsEnvelope(questions),\n };\n\n const supportsElicitation =\n !!server.server.getClientCapabilities()?.elicitation;\n\n // Capture into a local const so TS preserves the narrowing\n // inside the callback below. Optional chains don't survive\n // across closure boundaries under strict mode.\n const elicitInput = ctx.mcpReq?.elicitInput;\n\n if (supportsElicitation && elicitInput) {\n // Sequential — never parallel (day-one rule). We await the loop\n // before returning the tool result so test harnesses can observe\n // the dispatched calls deterministically.\n await dispatchElicitations({\n userId,\n questions,\n elicitInput: (params) => elicitInput(params),\n chatMessageWriter: deps.chatMessageWriter,\n });\n }\n\n return {\n content: [\n { type: \"text\" as const, text: sanitizedText },\n envelopeBlock,\n ],\n ...(toolIsError ? { isError: true } : {}),\n };\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: sanitizedText }],\n ...(toolIsError ? { isError: true } : {}),\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(`MCP tool \"${toolName}\" failed`, { error: message });\n if (shouldReportMcpToolError(err)) {\n reportDeps.reportToolError?.(err, {\n subsystem: 'mcp',\n operation: 'mcp.tool',\n toolName,\n userId: reportUserId,\n tags: {\n transport: 'mcp',\n toolName,\n },\n context: {\n agentId: reportContext?.agentId,\n networkId: reportContext?.networkId,\n indexScope: reportContext?.indexScope,\n },\n });\n }\n const runtimeResult = toolRuntimeErrorToResult(err);\n return {\n content: [{ type: 'text' as const, text: runtimeResult ?? JSON.stringify({ error: message }) }],\n isError: true,\n };\n }\n },\n );\n }\n\n logger.verbose(`MCP server created with ${registry.size} tools`);\n return server;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity.tools.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"opportunity.tools.d.ts","sourceRoot":"/","sources":["opportunity/opportunity.tools.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AA6B5E,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,4CAA4C,CAAC;AAEjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAC;AAOtF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,eAAe,GAAG,IAAI,CAczB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,iBAAiB,EAAE,MAAM,EACzB,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,MAAM,GAAG,SAAS,CAQpB;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,EACD,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,WAAW,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC1D,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,gBAAgB,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;CACvC,GACA,OAAO,CAAC,IAAI,CAAC,CA6Cf;AAuCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,MAAM,EACzB,eAAe,EAAE,MAAM,EACvB,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,EAC9B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,EAChC,UAAU,CAAC,EAAE,MAAM,EACnB,eAAe,CAAC,EAAE,MAAM,EACxB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,EACjC,iBAAiB,CAAC,EAAE,MAAM,EAC1B,kBAAkB,CAAC,EAAE,OAAO,GAC3B;IACD,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtF,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACzE,CA6DA;AAED;;;;GAIG;AACH,KAAK,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,mBAAmB,EAAE,EAC5B,IAAI,EAAE;IACJ,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,aAAa,GAAG,eAAe,CAAC;IACxC,8FAA8F;IAC9F,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,GACA,MAAM,CAyDR;AAuDD,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,2CAklD5E"}
@@ -316,6 +316,57 @@ export function buildOpportunityPresentation(cards, opts) {
316
316
  .join("\n\n");
317
317
  return (`${opts.leadIn} IMPORTANT: Include the following ${CODE_FENCE}${label} code blocks EXACTLY as-is in your response (they render as interactive cards):\n\n${blocks}`);
318
318
  }
319
+ /**
320
+ * Stable signature of a discovery request, used to coalesce duplicate MCP runs.
321
+ * Two requests with the same signature describe the same discovery and should
322
+ * share a single in-flight run rather than each spawning a fresh (expensive)
323
+ * opportunity-graph execution. Text fields are normalized (trim + lowercase);
324
+ * id lists are sorted so ordering does not matter.
325
+ *
326
+ * Encoded as JSON of a normalized object — NOT a delimiter join — so a
327
+ * user-supplied string containing the delimiter can never make two distinct
328
+ * requests collide. `hint` and each entity's `networkId` are included because
329
+ * they change the discovery result (different reason / different shared index),
330
+ * so requests that differ only in those must NOT coalesce. Natural-language
331
+ * fields (`searchQuery`, `hint`) are lowercased; identifiers and the opaque
332
+ * `continueFrom` pagination token are only trimmed (case-sensitive) so distinct
333
+ * tokens never collapse together.
334
+ */
335
+ function discoveryRunSignature(input, scopeKey) {
336
+ const text = (s) => (s ?? "").trim().toLowerCase();
337
+ const id = (s) => (s ?? "").trim();
338
+ // Encode each entity as a JSON tuple string, then sort the strings — a stable
339
+ // total order with a correct comparator (default Array#sort on strings).
340
+ const entities = [...(input.entities ?? [])]
341
+ .map((e) => JSON.stringify([id(e.networkId), id(e.userId)]))
342
+ .sort();
343
+ return JSON.stringify({
344
+ searchQuery: text(input.searchQuery),
345
+ networkId: id(input.networkId),
346
+ intentId: id(input.intentId),
347
+ targetUserId: id(input.targetUserId),
348
+ introTargetUserId: id(input.introTargetUserId),
349
+ continueFrom: id(input.continueFrom),
350
+ hint: text(input.hint),
351
+ partyUserIds: [...(input.partyUserIds ?? [])].map((x) => x.trim()).sort(),
352
+ entities,
353
+ scope: scopeKey,
354
+ });
355
+ }
356
+ /**
357
+ * Stable key for the resolved discovery scope of a request. Two requests
358
+ * coalesce only when they resolve to the same reach — the focus index
359
+ * (`networkId`) and the full reachable `indexScope` set. When `query.networkId`
360
+ * is omitted, scope comes from context (network-scoped agent vs unscoped
361
+ * session), so folding it into the signature prevents a scoped caller from
362
+ * coalescing onto an unscoped run and receiving out-of-scope opportunities.
363
+ */
364
+ function discoveryScopeKey(ctx) {
365
+ return JSON.stringify({
366
+ networkId: (ctx.networkId ?? "").trim(),
367
+ indexScope: [...(ctx.indexScope ?? [])].map((s) => s.trim()).sort(),
368
+ });
369
+ }
319
370
  export function createOpportunityTools(defineTool, deps) {
320
371
  const { database, userDb, systemDb, graphs, cache } = deps;
321
372
  const discoverOpportunities = defineTool({
@@ -333,7 +384,10 @@ export function createOpportunityTools(defineTool, deps) {
333
384
  "4. **Introducer discovery**: pass `introTargetUserId` (find matches FOR that person; current user becomes the introducer). " +
334
385
  "Use when user asks 'who should I introduce to [person]?'\n\n" +
335
386
  "**Returns:** In regular chat, opportunity code blocks (render as interactive cards) with opportunityId, match reasoning, confidence score, and status. " +
336
- "In MCP contexts, starts an async discovery run and returns `discoveryRunId`; poll get_discovery_run until status is `succeeded`, then present its `result`. " +
387
+ "In MCP contexts, starts an async discovery run and returns `discoveryRunId` with status `queued`. " +
388
+ "Then poll get_discovery_run with that id roughly every 5 seconds until status is `succeeded`, `failed`, or `cancelled`, and present its `result`. " +
389
+ "Do NOT call discover_opportunities again for the same request while a run is in progress — a repeat call with the same parameters " +
390
+ "returns the SAME in-progress run (with `coalesced: true`), not a new one. Keep polling the run id instead of starting new runs. " +
337
391
  "All results start as drafts. Supports pagination via `continueFrom` for large result sets.\n\n" +
338
392
  "**Next steps:** Use update_opportunity(opportunityId, status='pending') to send a draft to the other party.\n\n" +
339
393
  "**Discovery-first rule.** For open-ended connection-seeking requests (\"find me a mentor\", " +
@@ -440,6 +494,31 @@ export function createOpportunityTools(defineTool, deps) {
440
494
  return error("Invalid network ID format.");
441
495
  }
442
496
  if (context.isMcp && deps.discoveryRuns && deps.discoveryRunQueue) {
497
+ // Coalesce: if an equivalent discovery is already queued/running for this
498
+ // user, return that run instead of spawning a duplicate. An over-eager
499
+ // MCP client that re-fires discover_opportunities (instead of polling
500
+ // get_discovery_run) would otherwise kick off a fresh, expensive
501
+ // opportunity-graph run on every call — the loop that drives the agent
502
+ // into provider rate limits.
503
+ const signature = discoveryRunSignature(query, discoveryScopeKey(context));
504
+ try {
505
+ const active = await deps.discoveryRuns.listActive(context.userId);
506
+ const existing = active.find((r) => discoveryRunSignature(r.input, discoveryScopeKey(r.context)) === signature);
507
+ if (existing) {
508
+ return success({
509
+ status: existing.status === "running" ? "running" : "queued",
510
+ discoveryRunId: existing.id,
511
+ coalesced: true,
512
+ message: `A discovery run for this exact request is already ${existing.status}. ` +
513
+ `Do NOT call discover_opportunities again — keep calling get_discovery_run with ` +
514
+ `discoveryRunId="${existing.id}" (about every 5 seconds) until it succeeds, fails, ` +
515
+ `or is cancelled, then present its result.`,
516
+ });
517
+ }
518
+ }
519
+ catch {
520
+ // listActive is a best-effort optimization; fall through to create on error.
521
+ }
443
522
  const run = await deps.discoveryRuns.create({
444
523
  userId: context.userId,
445
524
  agentId: context.agentId ?? null,
@@ -467,7 +546,10 @@ export function createOpportunityTools(defineTool, deps) {
467
546
  return success({
468
547
  status: "queued",
469
548
  discoveryRunId: run.id,
470
- message: `Discovery started. Call get_discovery_run with discoveryRunId="${run.id}" until it succeeds, fails, or is cancelled.`,
549
+ message: `Discovery started. Poll get_discovery_run with discoveryRunId="${run.id}" about every 5 seconds ` +
550
+ `until status is succeeded, failed, or cancelled, then present its result. ` +
551
+ `Do NOT call discover_opportunities again for this request while the run is in progress — ` +
552
+ `a repeat call returns this same run, not a new one.`,
471
553
  });
472
554
  }
473
555
  // ── Continuation mode ──
@@ -1084,7 +1166,8 @@ export function createOpportunityTools(defineTool, deps) {
1084
1166
  const getDiscoveryRun = defineTool({
1085
1167
  name: "get_discovery_run",
1086
1168
  description: "Checks the status of an async discovery run started by discover_opportunities in MCP contexts. " +
1087
- "Poll this tool with the discoveryRunId until status is succeeded, failed, or cancelled. " +
1169
+ "Poll this tool with the discoveryRunId roughly every 5 seconds until status is succeeded, failed, or cancelled. " +
1170
+ "While status is queued or running, keep polling THIS tool — do NOT call discover_opportunities again (that does not speed anything up and returns the same run). " +
1088
1171
  "When succeeded, the result field contains the same discovery payload that discover_opportunities would have returned synchronously.",
1089
1172
  querySchema: z.object({
1090
1173
  discoveryRunId: z.string().describe("Discovery run ID returned by discover_opportunities."),