Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/http/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'dotenv/config';
import express, { Request, Response, NextFunction } from 'express';
import { randomUUID, createHash } from 'crypto';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

// Transport imports
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
Expand All @@ -24,6 +27,39 @@ import {
} from './config.js';
import { renderProjectSelectionPage } from './templates/project-selection.js';
import { getAnalyticsService, extractClientInfo } from './analytics.js';
import { TOOL_CATALOG } from '../shared/tool-catalog.js';

// ============================================================================
// Server Card Discovery Document
// ============================================================================

/** Path used by the MCP server-card discovery document (ora / agent readiness). */
const SERVER_CARD_ENDPOINT = '/.well-known/mcp/server-card.json';

/** Human-readable server description (kept in sync with repo-root server.json). */
const SERVER_DESCRIPTION =
'MCP server for InsForge BaaS — database, storage, edge functions, and deployments';

/**
* Resolve this package's version without importing package.json directly
* (rootDir is ./src, so a static import would fall outside the compile root).
* Reads package.json at runtime relative to the bundled module, falling back to
* npm's injected env var and finally a constant so the card always has a value.
*/
function resolveServerVersion(): string {
try {
const moduleDir = dirname(fileURLToPath(import.meta.url));
// dist output and src both sit one level below the package root.
const pkgRaw = readFileSync(join(moduleDir, '..', 'package.json'), 'utf-8');
const version = (JSON.parse(pkgRaw) as { version?: string }).version;
if (version) return version;
} catch {
// Ignore and fall through to env / constant fallbacks.
}
return process.env.npm_package_version || '0.0.0';
}

const SERVER_VERSION = resolveServerVersion();

// ============================================================================
// Express App Setup
Expand Down Expand Up @@ -144,6 +180,20 @@ app.get(API_ENDPOINTS.health, async (_req: Request, res: Response) => {
});
});

// MCP Server Card (ora / agent-readiness discovery document)
// Served UNAUTHENTICATED: it is a static discovery document and must not require
// OAuth or a backend connection. Exposes the five top-level fields ora's
// `mcp-server-card` check expects (name, description, version, serverUrl, tools).
app.get(SERVER_CARD_ENDPOINT, (_req: Request, res: Response) => {
res.json({
name: 'InsForge',
description: SERVER_DESCRIPTION,
version: SERVER_VERSION,
serverUrl: `${SERVER_CONFIG.publicUrl}${STREAMABLE_HTTP_ENDPOINTS.mcp}`,
tools: TOOL_CATALOG,
});
});

// OAuth 2.0 Authorization Server Metadata (RFC 8414)
// Uses SERVER_CONFIG.publicUrl as the canonical base URL to avoid host header spoofing
app.get(OAUTH_ENDPOINTS.metadata, (_req: Request, res: Response) => {
Expand Down
103 changes: 103 additions & 0 deletions src/shared/tool-catalog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Curated catalog of MCP tools exposed by the InsForge MCP server.
*
* This catalog backs the `/.well-known/mcp/server-card.json` discovery document
* (the ora / agent-readiness card). It is intentionally a flat, static list so the
* card can be served unauthenticated without standing up a session or backend
* connection.
*
* IMPORTANT: This list MUST stay in sync with the tools registered in
* `src/shared/tools/{database,storage,functions,deployment,docs}.ts`. The
* descriptions below are copied verbatim from the corresponding
* `registerTool('<name>', '<description>', ...)` calls. When a tool is added,
* removed, or its description changes, update this catalog as well.
*/
export const TOOL_CATALOG: { name: string; description: string }[] = [
// database.ts
{
name: 'bulk-upsert',
description:
'Bulk insert or update data from CSV or JSON file. Supports upsert operations with a unique key.',
},
{
name: 'get-backend-metadata',
description: 'Index all backend metadata',
},
{
name: 'get-table-schema',
description:
'Returns the detailed schema(including RLS, indexes, constraints, etc.) of a specific table',
},
{
name: 'run-raw-sql',
description:
'Execute raw SQL query with optional parameters. Admin access required. Use with caution as it can modify data directly.',
},
// storage.ts
{
name: 'create-bucket',
description: 'Create new storage bucket',
},
{
name: 'delete-bucket',
description: 'Deletes a storage bucket',
},
{
name: 'list-buckets',
description: 'Lists all storage buckets',
},
// functions.ts
{
name: 'create-function',
description: 'Create a new edge function that runs in Deno runtime',
},
{
name: 'delete-function',
description: 'Delete an edge function permanently',
},
{
name: 'get-function',
description: 'Get details of a specific edge function including its code',
},
{
name: 'update-function',
description: 'Update an existing edge function code or metadata',
},
// deployment.ts
{
name: 'create-deployment',
description:
'Prepare a deployment upload. Direct-capable backends return direct file upload commands. Older backends use the legacy zip upload flow. After uploading, call the start-deployment tool to trigger the build.',
},
{
name: 'get-container-logs',
description:
'Get latest logs from a specific container/service. Use this to help debug problems with your app.',
},
{
name: 'start-deployment',
description:
'Trigger a deployment build after uploading source code. Use this after executing the upload commands from create-deployment.',
},
// docs.ts
{
name: 'download-template',
description:
'CRITICAL: MANDATORY FIRST STEP for all new InsForge projects. Fetches configuration and returns a command for you to run locally to scaffold a starter template.',
},
{
name: 'fetch-docs',
description:
'Fetch Insforge documentation. Use "instructions" for essential backend setup (MANDATORY FIRST), or select specific SDK docs for database, auth, storage, functions, or AI integration.',
},
{
name: 'fetch-sdk-docs',
description:
'Fetch Insforge SDK documentation for a specific feature and language combination.',
},
{
name: 'get-anon-key',
description:
'Generate an anonymous JWT token that never expires. Requires admin API key. Use this for client-side applications that need public access.',
},
];
Loading