MCP Terminal Server

/** * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { runInActionRuntimeContext } from './action.js'; import { UserFacingError } from './error.js'; import { HasRegistry, Registry } from './registry.js'; const contextAlsKey = 'core.auth.context'; /** * Action side channel data, like auth and other invocation context infromation provided by the invoker. */ export interface ActionContext { /** Information about the currently authenticated user if provided. */ auth?: Record<string, any>; [additionalContext: string]: any; } /** * Execute the provided function in the runtime context. Call {@link getFlowContext()} anywhere * within the async call stack to retrieve the context. If context object is undefined, this function * is a no op passthrough, the function will be invoked as is. */ export function runWithContext<R>( registry: Registry, context: ActionContext | undefined, fn: () => R ): R { if (context === undefined) { return fn(); } return registry.asyncStore.run(contextAlsKey, context, () => runInActionRuntimeContext(registry, fn) ); } /** * Gets the runtime context of the current flow. */ export function getContext( registry: Registry | HasRegistry ): ActionContext | undefined { if ((registry as HasRegistry).registry) { registry = (registry as HasRegistry).registry; } registry = registry as Registry; return registry.asyncStore.getStore<ActionContext>(contextAlsKey); } /** * A universal type that request handling extensions (e.g. express, next) can map their request to. * This allows ContextProviders to build consistent interfacese on any web framework. * Headers must be lowercase to ensure portability. */ export interface RequestData<T = any> { method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'QUERY'; headers: Record<string, string>; input: T; } /** * Middleware can read request data and add information to the context that will * be passed to the Action. If middleware throws an error, that error will fail * the request and the Action will not be invoked. Expected cases should return a * UserFacingError, which allows the request handler to know what data is safe to * return to end users. * * Middleware can provide validation in addition to parsing. For example, an auth * middleware can have policies for validating auth in addition to passing auth context * to the Action. */ export type ContextProvider< C extends ActionContext = ActionContext, T = any, > = (request: RequestData<T>) => C | Promise<C>; export interface ApiKeyContext extends ActionContext { auth: { apiKey: string | undefined; }; } export function apiKey( policy: (context: ApiKeyContext) => void | Promise<void> ): ContextProvider<ApiKeyContext>; export function apiKey(value?: string): ContextProvider<ApiKeyContext>; export function apiKey( valueOrPolicy?: ((context: ApiKeyContext) => void | Promise<void>) | string ): ContextProvider<ApiKeyContext> { return async function (request: RequestData): Promise<ApiKeyContext> { const context: ApiKeyContext = { auth: { apiKey: request.headers['authorization'] }, }; if (typeof valueOrPolicy === 'string') { if (!context.auth?.apiKey) { console.error('THROWING UNAUTHENTICATED'); throw new UserFacingError('UNAUTHENTICATED', 'Unauthenticated'); } if (context.auth?.apiKey != valueOrPolicy) { console.error('Throwing PERMISSION_DENIED'); throw new UserFacingError('PERMISSION_DENIED', 'Permission Denied'); } } else if (typeof valueOrPolicy === 'function') { await valueOrPolicy(context); } else if (typeof valueOrPolicy !== 'undefined') { throw new Error( `Invalid type ${typeof valueOrPolicy} passed to apiKey()` ); } return context; }; }