Skip to content

[Feature Request] Expose the json schema object as a read-only attribute in the context #335

@islamzaoui

Description

@islamzaoui

Is there a way to access the JSON schema object from the context?

I want to make an MCP server for the OpenAPI documentation using elysia-mcp.

I'm currently stuck with a separate app:

import { mcp } from "elysia-mcp";
import { env } from "./core/env";
import { OpenAPIToolGenerator } from "mcp-from-openapi";
import { z } from "zod";

export const opeapiMCP = mcp({
	basePath: "/openapi",
	enableJsonResponse: true,
	serverInfo: {
		name: "Project API Documentation MCP",
		version: "unknown",
	},
	async setupServer(server) {
		const spec = (await fetch(`${env.BASE_API_URL}/openapi.json`).then((res) =>
			res.json()
		)) as object;

		const generator = await OpenAPIToolGenerator.fromJSON(spec);
		const tools = await generator.generateTools();

		for (const tool of tools) {
			server.registerTool(
				tool.name,
				{
					description: tool.description,
					inputSchema: z.fromJSONSchema(tool.inputSchema),
				},
				async (args) => {
					let path = tool.metadata.path;
					const headers: Record<string, string> = { "Content-Type": "application/json" };
					const query: Record<string, string> = {};
					const body: Record<string, unknown> = {};

					for (const { inputKey, key, type } of tool.mapper) {
						const value = (args as Record<string, unknown>)[inputKey];
						if (value === undefined) continue;
						if (type === "path") path = path.replace(`{${key}}`, String(value));
						else if (type === "query") query[key] = String(value);
						else if (type === "header") headers[key] = String(value);
						else if (type === "body") body[key] = value;
					}

					const qs = new URLSearchParams(query).toString();
					const url = `${env.BASE_API_URL}${path}${qs ? `?${qs}` : ""}`;

					const res = await fetch(url, {
						method: tool.metadata.method.toUpperCase(),
						headers,
						...(Object.keys(body).length && { body: JSON.stringify(body) }),
					});

					const text = await res.text();
					return {
						content: [{ type: "text" as const, text }],
						isError: !res.ok,
					};
				}
			);
		}
	},
});

If I can access the object, I would do something like this:

import OpenAPI from "@elysiajs/openapi";
import z from "zod";
import packageJson from "../../package.json";

export const openapi = OpenAPI({
	path: "/docs",
	specPath: "/openapi.json",
	mapJsonSchema: {
		zod: z.toJSONSchema,
	},
	documentation: {
		info: {
			title: packageJson.name,
			version: packageJson.version,
			description: packageJson.description,
		},
	},
	// @ts-expect-error - elysia didnt type this yet
	scalar: {
		defaultHttpClient: {
			targetKey: "js",
			clientKey: "fetch",
		},
	},
}).use((c) => {
	const { spec } = c;
	return mcp({
		basePath: "/openapi",
		enableJsonResponse: true,
		serverInfo: {
			name: "finals-project API Documentation MCP",
			version: "unknown",
		},
		async setupServer(server) {
			const generator = await OpenAPIToolGenerator.fromJSON(spec);
			const tools = await generator.generateTools();

			for (const tool of tools) {
				server.registerTool(
					tool.name,
					{
						description: tool.description,
						inputSchema: z.fromJSONSchema(tool.inputSchema),
					},
					async (args) => {
						let path = tool.metadata.path;
						const headers: Record<string, string> = { "Content-Type": "application/json" };
						const query: Record<string, string> = {};
						const body: Record<string, unknown> = {};

						for (const { inputKey, key, type } of tool.mapper) {
							const value = (args as Record<string, unknown>)[inputKey];
							if (value === undefined) continue;
							if (type === "path") path = path.replace(`{${key}}`, String(value));
							else if (type === "query") query[key] = String(value);
							else if (type === "header") headers[key] = String(value);
							else if (type === "body") body[key] = value;
						}

						const qs = new URLSearchParams(query).toString();
						const url = `${env.BASE_API_URL}${path}${qs ? `?${qs}` : ""}`;

						const res = await fetch(url, {
							method: tool.metadata.method.toUpperCase(),
							headers,
							...(Object.keys(body).length && { body: JSON.stringify(body) }),
						});

						const text = await res.text();
						return {
							content: [{ type: "text" as const, text }],
							isError: !res.ok,
						};
					}
				);
			}
		},
	});
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions