224 lines · 7.9 KB · TypeScript
RawDownload
import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";import { Schema } from "effect";import { InternalError, ScopeId, SecretBackedMap } from "@executor-js/sdk/core"; import { McpConnectionError, McpToolDiscoveryError } from "../sdk/errors";import { McpStoredSourceSchema } from "../sdk/stored-source";import {  McpConnectionAuthInput,  McpCredentialInput,  McpSourceBindingInput,  McpSourceBindingRef,} from "../sdk/types"; // ---------------------------------------------------------------------------// Params// --------------------------------------------------------------------------- const ScopeParams = { scopeId: ScopeId };const SourceParams = { scopeId: ScopeId, namespace: Schema.String };const SourceBindingParams = {  scopeId: ScopeId,  namespace: Schema.String,  sourceScopeId: ScopeId,}; // ---------------------------------------------------------------------------// Auth payload (only for remote)// --------------------------------------------------------------------------- const AuthPayload = McpConnectionAuthInput; const StringMap = Schema.Record(Schema.String, Schema.String);// ---------------------------------------------------------------------------// Add source — discriminated union on transport// --------------------------------------------------------------------------- const AddRemoteSourcePayload = Schema.Struct({  targetScope: ScopeId,  transport: Schema.Literal("remote"),  name: Schema.String,  endpoint: Schema.String,  remoteTransport: Schema.optional(Schema.Literals(["streamable-http", "sse", "auto"])),  namespace: Schema.optional(Schema.String),  queryParams: Schema.optional(Schema.Record(Schema.String, McpCredentialInput)),  headers: Schema.optional(Schema.Record(Schema.String, McpCredentialInput)),  auth: Schema.optional(AuthPayload),  credentialTargetScope: Schema.optional(ScopeId),}); const AddStdioSourcePayload = Schema.Struct({  targetScope: ScopeId,  transport: Schema.Literal("stdio"),  name: Schema.String,  command: Schema.String,  args: Schema.optional(Schema.Array(Schema.String)),  env: Schema.optional(StringMap),  cwd: Schema.optional(Schema.String),  namespace: Schema.optional(Schema.String),}); const AddSourcePayload = Schema.Union([AddRemoteSourcePayload, AddStdioSourcePayload]); // ---------------------------------------------------------------------------// Other payloads// --------------------------------------------------------------------------- const UpdateSourcePayload = Schema.Struct({  sourceScope: ScopeId,  name: Schema.optional(Schema.String),  endpoint: Schema.optional(Schema.String),  headers: Schema.optional(Schema.Record(Schema.String, McpCredentialInput)),  queryParams: Schema.optional(Schema.Record(Schema.String, McpCredentialInput)),  credentialTargetScope: Schema.optional(ScopeId),  auth: Schema.optional(AuthPayload),}); const UpdateSourceResponse = Schema.Struct({  updated: Schema.Boolean,}); const ProbeEndpointPayload = Schema.Struct({  endpoint: Schema.String,  headers: Schema.optional(SecretBackedMap),  queryParams: Schema.optional(SecretBackedMap),}); const ProbeEndpointResponse = Schema.Struct({  connected: Schema.Boolean,  requiresOAuth: Schema.Boolean,  supportsDynamicRegistration: Schema.Boolean,  name: Schema.String,  namespace: Schema.String,  toolCount: Schema.NullOr(Schema.Number),  serverName: Schema.NullOr(Schema.String),}); const NamespacePayload = Schema.Struct({  namespace: Schema.String,}); const RemoveBindingPayload = Schema.Struct({  sourceId: Schema.String,  sourceScope: ScopeId,  slot: Schema.String,  scope: ScopeId,}); // ---------------------------------------------------------------------------// Responses// --------------------------------------------------------------------------- const AddSourceResponse = Schema.Struct({  toolCount: Schema.Number,  namespace: Schema.String,}); const RefreshSourceResponse = Schema.Struct({  toolCount: Schema.Number,}); const RemoveSourceResponse = Schema.Struct({  removed: Schema.Boolean,}); // ---------------------------------------------------------------------------// Group//// Plugin SDK errors (McpOAuthError etc.) are declared once at the group// level via `.addError(...)` — every endpoint inherits them. The errors// themselves carry their HTTP status via `HttpApiSchema.annotations`// in errors.ts, so handlers just `return yield* ext.foo(...)` and the// schema encodes whatever it gets.//// 5xx is handled at the API level: `CoreExecutorApi.addError(InternalError)`// adds a single shared opaque-by-schema 500 surface to every endpoint in// the entire API. Defects are captured + downgraded to it by an// HttpApiBuilder middleware (see apps/cloud/src/observability.ts).// No per-handler wrapping, no per-plugin InternalError.// --------------------------------------------------------------------------- export const McpGroup = HttpApiGroup.make("mcp")  .add(    HttpApiEndpoint.post("probeEndpoint", "/scopes/:scopeId/mcp/probe", {      params: ScopeParams,      payload: ProbeEndpointPayload,      success: ProbeEndpointResponse,      error: [InternalError, McpConnectionError, McpToolDiscoveryError],    }),  )  .add(    HttpApiEndpoint.post("addSource", "/scopes/:scopeId/mcp/sources", {      params: ScopeParams,      payload: AddSourcePayload,      success: AddSourceResponse,      error: [InternalError, McpConnectionError, McpToolDiscoveryError],    }),  )  .add(    HttpApiEndpoint.post("removeSource", "/scopes/:scopeId/mcp/sources/remove", {      params: ScopeParams,      payload: NamespacePayload,      success: RemoveSourceResponse,      error: [InternalError, McpConnectionError, McpToolDiscoveryError],    }),  )  .add(    HttpApiEndpoint.post("refreshSource", "/scopes/:scopeId/mcp/sources/refresh", {      params: ScopeParams,      payload: NamespacePayload,      success: RefreshSourceResponse,      error: [InternalError, McpConnectionError, McpToolDiscoveryError],    }),  )  .add(    HttpApiEndpoint.get("getSource", "/scopes/:scopeId/mcp/sources/:namespace", {      params: SourceParams,      success: Schema.NullOr(McpStoredSourceSchema),      error: [InternalError, McpConnectionError, McpToolDiscoveryError],    }),  )  .add(    HttpApiEndpoint.patch("updateSource", "/scopes/:scopeId/mcp/sources/:namespace", {      params: SourceParams,      payload: UpdateSourcePayload,      success: UpdateSourceResponse,      error: [InternalError, McpConnectionError, McpToolDiscoveryError],    }),  )  .add(    HttpApiEndpoint.get(      "listSourceBindings",      "/scopes/:scopeId/mcp/sources/:namespace/base/:sourceScopeId/bindings",      {        params: SourceBindingParams,        success: Schema.Array(McpSourceBindingRef),        error: [InternalError, McpConnectionError, McpToolDiscoveryError],      },    ),  )  .add(    HttpApiEndpoint.post("setSourceBinding", "/scopes/:scopeId/mcp/source-bindings", {      params: ScopeParams,      payload: McpSourceBindingInput,      success: McpSourceBindingRef,      error: [InternalError, McpConnectionError, McpToolDiscoveryError],    }),  )  .add(    HttpApiEndpoint.post("removeSourceBinding", "/scopes/:scopeId/mcp/source-bindings/remove", {      params: ScopeParams,      payload: RemoveBindingPayload,      success: Schema.Struct({ removed: Schema.Boolean }),      error: [InternalError, McpConnectionError, McpToolDiscoveryError],    }),  );// Errors declared once at the group level — every endpoint inherits.// Plugin domain errors carry their own HttpApiSchema status (4xx);// `InternalError` is the shared opaque 500 translated at the HTTP// edge by `withCapture`. We only list errors an MCP *group*// endpoint can surface: `McpInvocationError` is thrown inside// `invokeTool` which is reached via the core `tools.invoke`// endpoint, not any MCP-group endpoint, so it doesn't belong here.// OAuth errors live on the shared `/oauth/*` group in `@executor-js/api`// now — the MCP group only declares its own plugin-domain errors.