Trovella Wiki

Resources and Events

MCP resources exposed by the server and Inngest events emitted by tools for background processing.

Beyond tools, the MCP server exposes resources (read-only data the client can access) and certain tools emit events to trigger background processing via Inngest.

MCP Resources

The MCP specification defines resources as read-only data sources that clients can retrieve. Trovella registers one resource.

trovella://user/profile

Returns the authenticated user's identity as JSON. Registered in packages/mcp/src/resources/profile.ts.

URI: trovella://user/profile

MIME type: application/json

Response:

{
  "id": "usr_abc123",
  "name": "Kyle Olson",
  "email": "kyle@trovella.ai"
}

The data comes directly from the McpAuthContext populated during PAT authentication -- no database query is required. This resource is useful for MCP clients that want to confirm the authenticated identity without calling the ping tool.

Adding a New Resource

New resources are registered in packages/mcp/src/server.ts alongside tools:

server.registerResource(
  "Display Name",
  "trovella://category/resource-name",
  { description: "...", mimeType: "application/json" },
  async () => ({
    contents: [
      {
        uri: "trovella://category/resource-name",
        mimeType: "application/json",
        text: JSON.stringify(data),
      },
    ],
  }),
);

Resources do not go through withToolCallLogging -- the middleware only wraps tool registrations. If a resource needs tenant-scoped data, the callback should call resolveOrganizationId() and withTenantContext() directly, following the same pattern as tool callbacks.

Inngest Event Emission

Two MCP tools emit events to trigger background processing via Inngest. The event emitter lives in packages/mcp/src/events.ts and the consumer functions live in apps/web/src/inngest/functions/. This separation means MCP tools are unaware of what processes their events.

Event: search/content.created

Triggers hybrid search indexing (chunking, contextual prefix generation, embedding, dual-write to pgvector and Typesense).

Emitted by:

  • store_research -- when contentText is provided
  • store_research_output -- when contentSummary is provided

Event data:

FieldTypeDescription
sourceTableenumresearch_artifact, research_output, or extraction_result
sourceIdstringUUID of the stored record
titlestringRecord title
contentstringText content for indexing
organizationIdstringTenant ID
userIdstringCreator's user ID
artifactTypestring(optional) Artifact type, for research_artifact
mediaTypestring(optional) Media type, for research_output
planIdstring(optional) Associated plan ID

Graceful Degradation

The emitContentCreated() function follows a no-op pattern when Inngest is not configured:

function getInngest(): Inngest | null {
  if (!process.env["INNGEST_BASE_URL"] && !process.env["INNGEST_DEV"]) {
    return null;  // No Inngest? No-op.
  }
  // Lazy initialization on first use
  if (!inngest) {
    inngest = new Inngest({ id: "trovella" });
  }
  return inngest;
}

export async function emitContentCreated(data: { ... }): Promise<void> {
  const client = getInngest();
  if (!client) return;  // Silent no-op
  await client.send({ name: "search/content.created", data });
}

This means:

  • Database seeding works without Inngest running (the emitter returns silently)
  • Tests do not need to mock Inngest (unless testing the event emission itself)
  • Local development works with or without the Inngest dev server

Event Flow Diagram

store_research()           store_research_output()
       |                            |
       v                            v
  contentText?                contentSummary?
       |                            |
       v                            v
  emitContentCreated()        emitContentCreated()
       |                            |
       +--------+   +--------------+
                |   |
                v   v
         Inngest event queue
                |
                v
         index-content function (4 durable steps)
           Step 1: chunk-content
           Step 2: generate-context
           Step 3: embed-chunks
           Step 4: store-and-sync

For the full event flow, consumer function details, and the retry/concurrency model, see Data & Storage -- Event Patterns.

Tools That Do Not Emit Events

Most MCP tools interact only with the database and do not emit background events. Notably, extract_data writes to extraction_result but does not currently emit search/content.created. This means extraction results are stored but not indexed for hybrid search. This is intentional for MVP -- extraction results are structured data (JSON) rather than prose, and the search pipeline is optimized for text content.

On this page