Agent / foldkit-agent-mcp

AgentMcp

namespace · packages/agent-mcp/src/agentMcp.ts:1

export * as AgentMcp from './agentMcp.js'

Members

AgentMcp.Handler

interface · packages/agent-mcp/src/handler.ts:33

interface Handler {
  readonly handle: (message: unknown) => Promise<Response | undefined>
  readonly close: () => void
}

AgentMcp.handler

function · packages/agent-mcp/src/handler.ts:64

function handler<Model, Context_, Principal, ByName, ByTag>(
  options: HandlerOptions<Model, Context_, Principal, ByName, ByTag>,
): Handler

Maps a bound agent contract onto MCP, with no transport attached.

Every protocol decision lives here, so it can be tested by handing it messages.

AgentMcp.HandlerOptions

interface · packages/agent-mcp/src/handler.ts:19

interface HandlerOptions<Model, Context_, Principal, ByName, ByTag> {
  readonly agent: Agent.AgentRuntime<Model, Context_, Principal, ByName, ByTag>
  readonly serverInfo?:
    | {
        readonly name: string
        readonly version: string
      }
    | undefined
  readonly onNotification?: ((notification: Notification) => void) | undefined
  readonly debounceMs?: number | undefined
}

AgentMcp.httpApp

function · packages/agent-mcp/src/httpApp.ts:78

function httpApp<Model, Context_, Principal, ByName, ByTag>(
  options: HttpAppOptions<Model, Context_, Principal, ByName, ByTag>,
): Effect.Effect<
  HttpServerResponse.HttpServerResponse,
  never,
  HttpServerRequest.HttpServerRequest
>

Serves a contract as an Effect HTTP application.

This is the entry point to prefer: it runs behind any Effect HTTP server, or as a plain web handler via HttpEffect.toWebHandler, on Node, Bun, Deno or a worker. The session and security rules live in the transport-neutral core; this is the I/O.

Example

ts
const handler = HttpEffect.toWebHandler(AgentMcp.httpApp({ createAgent, authenticate }))

AgentMcp.HttpAppOptions

type · packages/agent-mcp/src/httpApp.ts:59

type HttpAppOptions<Model, Context_, Principal, ByName, ByTag> =
  | (HttpHandlerOptions<Model, Context_, Principal, ByName, ByTag> & {
      readonly server?: undefined
    })
  | {
      readonly server: HttpHandler
    }

Either the options to create a handler, or an existing handler to reuse so sessions can be shared. Not both: a reused handler would ignore the rest.

AgentMcp.HttpHandler

interface · packages/agent-mcp/src/http.ts:124

interface HttpHandler {
  readonly handle: (request: HttpRequest) => Promise<HttpResponse>
  readonly sessions: () => ReadonlyArray<string>
  readonly close: () => void
}

AgentMcp.httpHandler

function · packages/agent-mcp/src/http.ts:227

function httpHandler<Model, Context_, Principal, ByName, ByTag>(
  options: HttpHandlerOptions<Model, Context_, Principal, ByName, ByTag>,
): HttpHandler

Serves a contract over the Streamable HTTP transport.

One endpoint answers POST, GET and DELETE. A session is created on initialize and identified by Mcp-Session-Id afterwards; an unknown session is a 404 so the client re-initializes.

AgentMcp.HttpHandlerOptions

interface · packages/agent-mcp/src/http.ts:70

interface HttpHandlerOptions<Model, Context_, Principal, ByName, ByTag> {
  readonly createAgent: (context: {
    readonly principal: Principal
    readonly sessionId: string
  }) => Agent.AgentRuntime<Model, Context_, Principal, ByName, ByTag>
  readonly authenticate?:
    | ((request: HttpRequest) => Effect.Effect<Principal, Unauthenticated>)
    | undefined
  readonly principalId?: ((principal: Principal) => string) | undefined
  readonly allowedOrigins?: ReadonlyArray<string> | undefined
  readonly sessionTtlMs?: number | undefined
  readonly clock?: (() => number) | undefined
  readonly replayBuffer?: number | undefined
  readonly serverInfo?:
    | {
        readonly name: string
        readonly version: string
      }
    | undefined
}

AgentMcp.HttpRequest

interface · packages/agent-mcp/src/http.ts:15

interface HttpRequest {
  readonly method: string
  readonly headers: Readonly<Record<string, string | undefined>>
  readonly body?: unknown
}

A transport-neutral request, so this can sit behind any HTTP server.

AgentMcp.HttpResponse

interface · packages/agent-mcp/src/http.ts:48

interface HttpResponse {
  readonly status: number
  readonly headers: Readonly<Record<string, string>>
  readonly body?: unknown
  readonly stream?: SseStream
}

AgentMcp.LineInput

interface · packages/agent-mcp/src/stdio.ts:5

interface LineInput {
  on(event: 'data', listener: (chunk: Buffer | string) => void): unknown
  on(event: 'end', listener: () => void): unknown
  off(event: 'data', listener: (chunk: Buffer | string) => void): unknown
  off(event: 'end', listener: () => void): unknown
}

The part of a readable stream stdio uses. process.stdin satisfies it.

AgentMcp.LineOutput

interface · packages/agent-mcp/src/stdio.ts:13

interface LineOutput {
  write(line: string): unknown
}

The part of a writable stream stdio uses. process.stdout satisfies it.

AgentMcp.PROTOCOL_VERSION

const · packages/agent-mcp/src/handler.ts:17

const PROTOCOL_VERSION: '2025-06-18'

The protocol version this handler speaks.

AgentMcp.SseEvent

interface · packages/agent-mcp/src/http.ts:24

interface SseEvent {
  readonly id: string
  readonly data: string
}

One server-sent event. id is the cursor a client resumes from.

AgentMcp.SseStream

interface · packages/agent-mcp/src/http.ts:30

interface SseStream {
  readonly backlog: ReadonlyArray<SseEvent>
  readonly subscribe: (
    send: (event: SseEvent) => void,
    end?: () => void,
  ) => () => void
}

An open SSE stream.

AgentMcp.stdio

function · packages/agent-mcp/src/stdio.ts:28

function stdio<Model, Context_, Principal, ByName, ByTag>(
  options: Omit<
    HandlerOptions<Model, Context_, Principal, ByName, ByTag>,
    'onNotification'
  > &
    StdioOptions,
): Handler

Serves a contract as newline-delimited JSON-RPC over stdin and stdout.

stdout carries MCP messages and nothing else -- a stray console.log corrupts the stream, which is why diagnostics belong on stderr.

AgentMcp.StdioOptions

interface · packages/agent-mcp/src/stdio.ts:17

interface StdioOptions {
  readonly input?: LineInput | undefined
  readonly output?: LineOutput | undefined
}

AgentMcp.Unauthenticated

interface · packages/agent-mcp/src/http.ts:61

interface Unauthenticated {
  readonly _tag: 'Unauthenticated'
  readonly reason?: string | undefined
}

Why authenticate refused a caller: answered as a 401 with reason. Remote's RemoteUnauthenticated (RemoteServer.unauthenticated(reason)) is one, so the resolver a Remote host uses can refuse here too.

AgentMcp.unauthenticated

function · packages/agent-mcp/src/http.ts:67

function unauthenticated(reason?: string): Unauthenticated

Refuses a caller from authenticate: Effect.fail(AgentMcp.unauthenticated('Token expired')).