Skip to main content
Glama
schema.ts9.04 kB
import { SuperSchema } from "../types.ts"; import { JSONSchema } from "../draft_07.ts"; import { Extend } from "../../extend.ts"; export type JsonSchema = JSONSchema; // After dereferencing, all schemas are objects (no boolean/null) // Make it recursive so nested properties are also JsonSchemaObject export type JsonSchemaObject = & Omit< Exclude<JSONSchema, boolean | null>, | "properties" | "additionalProperties" | "items" | "allOf" | "oneOf" | "anyOf" > & { properties?: Record<string, JsonSchemaObject>; additionalProperties?: JsonSchemaObject; items?: JsonSchemaObject; allOf?: readonly JsonSchemaObject[]; oneOf?: readonly JsonSchemaObject[]; anyOf?: readonly JsonSchemaObject[]; }; export type NormalizedGcpSchema = Extend< JsonSchemaObject, { allOf?: readonly NormalizedGcpSchema[]; oneOf?: readonly NormalizedGcpSchema[]; anyOf?: readonly NormalizedGcpSchema[]; items?: NormalizedGcpSchema; properties?: Record<string, NormalizedGcpSchema>; additionalProperties?: NormalizedGcpSchema; // annotations.required lists methods that require this property annotations?: { required?: string[]; }; } >; export interface GcpDiscoveryDocument { kind: "discovery#restDescription"; name: string; version: string; title: string; description?: string; documentationLink?: string; baseUrl: string; basePath: string; rootUrl: string; servicePath: string; parameters?: Record<string, GcpParameter>; auth?: GcpAuth; schemas: Record<string, NormalizedGcpSchema>; resources?: Record<string, GcpResource>; methods?: Record<string, GcpMethod>; } export interface GcpResource { methods?: Record<string, GcpMethod>; resources?: Record<string, GcpResource>; } export interface GcpMethod { id: string; path: string; httpMethod: string; description?: string; parameters?: Record<string, GcpParameter>; parameterOrder?: string[]; request?: NormalizedGcpSchema; response?: NormalizedGcpSchema; scopes?: string[]; supportsMediaDownload?: boolean; supportsMediaUpload?: boolean; } export interface GcpParameter { type: string; description?: string; required?: boolean; location: "path" | "query"; pattern?: string; minimum?: string; maximum?: string; default?: string; } export interface GcpAuth { oauth2?: { scopes: Record<string, { description: string }>; }; } export interface GcpSchema extends SuperSchema { requiredProperties: Set<string>; service: string; title: string; version: string; resourcePath: string[]; baseUrl: string; documentationLink?: string; // API methods for CRUD operations methods: { get?: GcpMethod; insert?: GcpMethod; update?: GcpMethod; patch?: GcpMethod; delete?: GcpMethod; list?: GcpMethod; }; } // Raw (un-dereferenced) types from GCP Discovery documents // In GCP Discovery format, request/response are ALWAYS { $ref: string }, never inline interface RawGcpMethod { id: string; path: string; httpMethod: string; description?: string; parameters?: Record<string, GcpParameter>; parameterOrder?: string[]; request?: { $ref: string }; response?: { $ref: string }; scopes?: string[]; supportsMediaDownload?: boolean; supportsMediaUpload?: boolean; } interface RawGcpResource { methods?: Record<string, RawGcpMethod>; resources?: Record<string, RawGcpResource>; } interface RawGcpDiscoveryDocument { kind: "discovery#restDescription"; name: string; version: string; title: string; description?: string; documentationLink?: string; baseUrl: string; basePath: string; rootUrl: string; servicePath: string; parameters?: Record<string, GcpParameter>; auth?: GcpAuth; schemas: Record<string, NormalizedGcpSchema>; resources?: Record<string, RawGcpResource>; methods?: Record<string, RawGcpMethod>; } export async function readGcpDiscoveryDocument( filePath: string, ): Promise<GcpDiscoveryDocument> { const content = await Deno.readTextFile(filePath); const doc = JSON.parse(content) as RawGcpDiscoveryDocument; // Dereference $ref fields in the GCP Discovery document // Note that this is a different $ref than OpenApi specs, but simpler in that // it just references within the same file by name return dereferenceGcpDiscoveryDocument(doc); } /** * Dereferences $ref fields in a GCP Discovery document. * * GCP Discovery documents use a simpler $ref system than OpenAPI - they reference * schemas by name within the same document's schemas object. This function * dereferences: * 1. Method-level refs (method.request.$ref, method.response.$ref) * 2. Schema-level refs (nested within properties, items, additionalProperties) * * Example transformation: * Before: method.request = { $ref: "Instance" } * After: method.request = { type: "object", properties: { name: {...}, ... } } * * Circular references are handled by replacing them with a placeholder object. * * @param doc - Raw GCP Discovery document with $ref references * @returns Fully dereferenced document with resolved schema objects */ function dereferenceGcpDiscoveryDocument( doc: RawGcpDiscoveryDocument, ): GcpDiscoveryDocument { const schemas = doc.schemas || {}; const dereferencedSchemas = dereferenceSchemas(schemas); // Create a dereferenced copy of the document const dereferenced: GcpDiscoveryDocument = { ...doc, schemas: dereferencedSchemas, resources: doc.resources ? dereferenceResources(doc.resources, dereferencedSchemas) : undefined, methods: doc.methods ? dereferenceMethods(doc.methods, dereferencedSchemas) : undefined, }; return dereferenced; } function dereferenceSchemas( schemas: Record<string, NormalizedGcpSchema>, ): Record<string, NormalizedGcpSchema> { const dereferenced: Record<string, NormalizedGcpSchema> = {}; for (const [name, schema] of Object.entries(schemas)) { dereferenced[name] = dereferenceSchema(schema, schemas, new Set()); } return dereferenced; } function dereferenceSchema( schema: NormalizedGcpSchema, allSchemas: Record<string, NormalizedGcpSchema>, visited: Set<string>, ): NormalizedGcpSchema { // Handle $ref - dereference by looking up in allSchemas if (hasRef(schema)) { if (visited.has(schema.$ref)) { return { type: "object", description: `Circular reference to ${schema.$ref}`, }; } visited.add(schema.$ref); const referencedSchema = allSchemas[schema.$ref]; if (referencedSchema) { return dereferenceSchema(referencedSchema, allSchemas, visited); } } const dereferenced: NormalizedGcpSchema = { ...schema }; if (schema.properties) { dereferenced.properties = {}; for (const [key, value] of Object.entries(schema.properties)) { dereferenced.properties[key] = dereferenceSchema( value, allSchemas, new Set(visited), ); } } if (schema.items) { dereferenced.items = dereferenceSchema( schema.items, allSchemas, new Set(visited), ); } if (schema.additionalProperties) { dereferenced.additionalProperties = dereferenceSchema( schema.additionalProperties, allSchemas, new Set(visited), ); } if (schema.allOf) { dereferenced.allOf = schema.allOf.map((s) => dereferenceSchema(s, allSchemas, new Set(visited)) ); } if (schema.oneOf) { dereferenced.oneOf = schema.oneOf.map((s) => dereferenceSchema(s, allSchemas, new Set(visited)) ); } if (schema.anyOf) { dereferenced.anyOf = schema.anyOf.map((s) => dereferenceSchema(s, allSchemas, new Set(visited)) ); } return dereferenced; } function hasRef( schema: NormalizedGcpSchema, ): schema is NormalizedGcpSchema & { $ref: string } { return typeof schema === "object" && schema !== null && "$ref" in schema && typeof schema.$ref === "string"; } function dereferenceMethods( methods: Record<string, RawGcpMethod>, schemas: Record<string, NormalizedGcpSchema>, ): Record<string, GcpMethod> { const dereferenced: Record<string, GcpMethod> = {}; for (const [name, method] of Object.entries(methods)) { dereferenced[name] = { ...method, request: method.request ? schemas[method.request.$ref] : undefined, response: method.response ? schemas[method.response.$ref] : undefined, }; } return dereferenced; } function dereferenceResources( resources: Record<string, RawGcpResource>, schemas: Record<string, NormalizedGcpSchema>, ): Record<string, GcpResource> { const dereferenced: Record<string, GcpResource> = {}; for (const [name, resource] of Object.entries(resources)) { dereferenced[name] = { methods: resource.methods ? dereferenceMethods(resource.methods, schemas) : undefined, resources: resource.resources ? dereferenceResources(resource.resources, schemas) : undefined, }; } return dereferenced; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server