mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
Remove mcp-server
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Emulator Test / docker (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Test / docker (push) Has been cancelled
Runs E2E API Tests / build (22.x) (push) Has been cancelled
Runs E2E API Tests with external source of truth / build (22.x) (push) Has been cancelled
Lint & build / lint_and_build (latest) (push) Has been cancelled
Dev Environment Test / restart-dev-and-test (push) Has been cancelled
Run setup tests / setup-tests (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
Docker Emulator Test / docker (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Test / docker (push) Has been cancelled
Runs E2E API Tests / build (22.x) (push) Has been cancelled
Runs E2E API Tests with external source of truth / build (22.x) (push) Has been cancelled
Lint & build / lint_and_build (latest) (push) Has been cancelled
Dev Environment Test / restart-dev-and-test (push) Has been cancelled
Run setup tests / setup-tests (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
This commit is contained in:
parent
ddf1cfd01a
commit
5ee342af7c
@ -17,7 +17,7 @@
|
||||
"codegen-prisma:watch": "pnpm run prisma generate --watch",
|
||||
"codegen-route-info": "pnpm run with-env tsx scripts/generate-route-info.ts",
|
||||
"codegen-route-info:watch": "pnpm run with-env tsx watch --clear-screen=false scripts/generate-route-info.ts",
|
||||
"codegen": "pnpm run with-env pnpm run generate-migration-imports && pnpm run with-env bash -c 'if [ \"$STACK_ACCELERATE_ENABLED\" = \"true\" ]; then pnpm run prisma generate --no-engine && pnpm run generate-openapi; else pnpm run codegen-prisma && pnpm run generate-openapi; fi' && pnpm run codegen-route-info",
|
||||
"codegen": "pnpm run with-env pnpm run generate-migration-imports && pnpm run with-env bash -c 'if [ \"$STACK_ACCELERATE_ENABLED\" = \"true\" ]; then pnpm run prisma generate --no-engine; else pnpm run codegen-prisma; fi' && pnpm run codegen-route-info",
|
||||
"codegen:watch": "concurrently -n \"prisma,docs,route-info\" -k \"pnpm run codegen-prisma:watch\" \"pnpm run watch-docs\" \"pnpm run codegen-route-info:watch\"",
|
||||
"psql-inner": "psql $STACK_DATABASE_CONNECTION_STRING",
|
||||
"psql": "pnpm run with-env pnpm run psql-inner",
|
||||
@ -32,7 +32,6 @@
|
||||
"generate-migration-imports:watch": "chokidar 'prisma/migrations/**/*.sql' -c 'pnpm run generate-migration-imports'",
|
||||
"lint": "next lint",
|
||||
"watch-docs": "pnpm run with-env bash -c 'tsx watch --clear-screen=false scripts/generate-openapi-fumadocs.ts && pnpm run --filter=@stackframe/stack-docs generate-openapi-docs'",
|
||||
"generate-openapi": "pnpm run with-env tsx scripts/generate-openapi.ts",
|
||||
"generate-openapi-fumadocs": "pnpm run with-env tsx scripts/generate-openapi-fumadocs.ts",
|
||||
"generate-keys": "pnpm run with-env tsx scripts/generate-keys.ts",
|
||||
"db-seed-script": "pnpm run with-env tsx prisma/seed.ts",
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
import { parseOpenAPI } from '@/lib/openapi';
|
||||
import { isSmartRouteHandler } from '@/route-handlers/smart-route-handler';
|
||||
import { writeFileSyncIfChanged } from '@stackframe/stack-shared/dist/utils/fs';
|
||||
import { HTTP_METHODS } from '@stackframe/stack-shared/dist/utils/http';
|
||||
import { typedKeys } from '@stackframe/stack-shared/dist/utils/objects';
|
||||
import { glob } from 'glob';
|
||||
import path from 'path';
|
||||
|
||||
async function main() {
|
||||
console.log("Started docs schema generator");
|
||||
|
||||
for (const audience of ['client', 'server', 'admin'] as const) {
|
||||
const filePathPrefix = path.resolve(process.platform === "win32" ? "apps/src/app/api/latest" : "src/app/api/latest");
|
||||
const importPathPrefix = "@/app/api/latest";
|
||||
const filePaths = [...await glob(filePathPrefix + "/**/route.{js,jsx,ts,tsx}")];
|
||||
|
||||
const openApiSchemaObject = parseOpenAPI({
|
||||
endpoints: new Map(await Promise.all(filePaths.map(async (filePath) => {
|
||||
if (!filePath.startsWith(filePathPrefix)) {
|
||||
throw new Error(`Invalid file path: ${filePath}`);
|
||||
}
|
||||
const suffix = filePath.slice(filePathPrefix.length);
|
||||
const midfix = suffix.slice(0, suffix.lastIndexOf("/route."));
|
||||
const importPath = `${importPathPrefix}${suffix}`;
|
||||
const urlPath = midfix.replaceAll("[", "{").replaceAll("]", "}").replaceAll(/\/\(.*\)/g, "");
|
||||
const myModule = require(importPath);
|
||||
const handlersByMethod = new Map(
|
||||
typedKeys(HTTP_METHODS).map(method => [method, myModule[method]] as const)
|
||||
.filter(([_, handler]) => isSmartRouteHandler(handler))
|
||||
);
|
||||
return [urlPath, handlersByMethod] as const;
|
||||
}))),
|
||||
audience,
|
||||
});
|
||||
writeFileSyncIfChanged(`../mcp-server/openapi/${audience}.json`, JSON.stringify(openApiSchemaObject, null, 2));
|
||||
}
|
||||
console.log("Successfully updated docs schemas");
|
||||
}
|
||||
main().catch((...args) => {
|
||||
console.error(`ERROR! Could not update OpenAPI schema`, ...args);
|
||||
process.exit(1);
|
||||
});
|
||||
@ -13,7 +13,7 @@ import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/proje
|
||||
import { UsersCrud } from "@stackframe/stack-shared/dist/interface/crud/users";
|
||||
import { StackAdaptSentinel, yupValidate } from "@stackframe/stack-shared/dist/schema-fields";
|
||||
import { groupBy, typedIncludes } from "@stackframe/stack-shared/dist/utils/arrays";
|
||||
import { getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { getEnvVariable, getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { StackAssertionError, StatusError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
|
||||
import { deindent } from "@stackframe/stack-shared/dist/utils/strings";
|
||||
import { NextRequest } from "next/server";
|
||||
@ -259,7 +259,7 @@ const parseAuth = withTraceSpan('smart request parseAuth', async (req: NextReque
|
||||
const tenancy = req.method === "GET" && req.url.endsWith("/users/me") ? "tenancy not available in /users/me as a performance hack" as never : await getSoleTenancyFromProjectBranch(projectId, branchId, true);
|
||||
|
||||
if (developmentKeyOverride) {
|
||||
if (getNodeEnvironment() !== "development" && getNodeEnvironment() !== "test") {
|
||||
if (!["development", "test"].includes(getNodeEnvironment()) && getEnvVariable("STACK_ALLOW_DEVELOPMENT_KEY_OVERRIDE_DESPITE_PRODUCTION", "") === "this-is-dangerous") { // it's not actually that dangerous, but it changes the security model
|
||||
throw new StatusError(401, "Development key override is only allowed in development or test environments");
|
||||
}
|
||||
const result = await checkApiKeySet("internal", { superSecretAdminKey: developmentKeyOverride });
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
STACK_AUTH_URL=http://localhost:8102
|
||||
STACK_PROJECT_ID=internal
|
||||
STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only
|
||||
STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only
|
||||
@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../configs/eslint/defaults.js",
|
||||
"../../configs/eslint/next.js",
|
||||
],
|
||||
"ignorePatterns": ['/*', '!/src']
|
||||
};
|
||||
@ -1,183 +0,0 @@
|
||||
# @stackframe/mcp-server
|
||||
|
||||
## 2.8.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.26
|
||||
|
||||
## 2.8.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.25
|
||||
|
||||
## 2.8.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.24
|
||||
|
||||
## 2.8.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.23
|
||||
|
||||
## 2.8.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.22
|
||||
|
||||
## 2.8.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.21
|
||||
|
||||
## 2.8.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.20
|
||||
|
||||
## 2.8.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.19
|
||||
|
||||
## 2.8.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.18
|
||||
|
||||
## 2.8.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.17
|
||||
|
||||
## 2.8.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.16
|
||||
|
||||
## 2.8.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.15
|
||||
|
||||
## 2.8.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.14
|
||||
|
||||
## 2.8.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.13
|
||||
|
||||
## 2.8.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Various changes
|
||||
- @stackframe/js@2.8.12
|
||||
|
||||
## 2.8.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.11
|
||||
|
||||
## 2.8.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.10
|
||||
|
||||
## 2.8.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Various changes
|
||||
- @stackframe/js@2.8.9
|
||||
|
||||
## 2.8.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.8
|
||||
|
||||
## 2.8.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.7
|
||||
|
||||
## 2.8.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.6
|
||||
|
||||
## 2.8.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.5
|
||||
|
||||
## 2.8.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.4
|
||||
|
||||
## 2.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @stackframe/js@2.8.3
|
||||
|
||||
## 2.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.2
|
||||
|
||||
## 2.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.8.1
|
||||
|
||||
## 2.8.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @stackframe/js@2.8.0
|
||||
|
||||
## 2.7.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.7.30
|
||||
|
||||
## 2.7.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @stackframe/js@2.7.29
|
||||
|
||||
## 2.7.27
|
||||
|
||||
initial release
|
||||
1
apps/mcp-server/openapi/.gitignore
vendored
1
apps/mcp-server/openapi/.gitignore
vendored
@ -1 +0,0 @@
|
||||
*.json
|
||||
@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "@stackframe/mcp-server",
|
||||
"version": "2.8.26",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"mcp-server": "./build/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "dotenv -c local -- tsx src/index.ts",
|
||||
"dev-mcp-server": "dotenv -c development -- tsx watch --clear-screen=false src/index.ts",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint .",
|
||||
"clean": "rimraf build && rimraf node_modules && rimraf openapi/*.json",
|
||||
"build": "tsc"
|
||||
},
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.7.0",
|
||||
"@stackframe/js": "workspace:*",
|
||||
"dotenv-cli": "^7.3.0",
|
||||
"openapi-types": "^12.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.17.6",
|
||||
"typescript": "5.3.3"
|
||||
}
|
||||
}
|
||||
@ -1,229 +0,0 @@
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
ListToolsResult
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { StackServerApp, stackAppInternalsSymbol } from "@stackframe/js";
|
||||
import { readFileSync } from "fs";
|
||||
import type { OpenAPIV3_1 } from 'openapi-types';
|
||||
import { convertParameterArrayToJsonSchema } from "./utils/openapi-to-jsonschema";
|
||||
|
||||
|
||||
const STACK_AUTH_URL = process.env.STACK_AUTH_URL ?? "https://api.stack-auth.com/";
|
||||
const STACK_SECRET_SERVER_KEY = process.env.STACK_SECRET_SERVER_KEY;
|
||||
const STACK_PROJECT_ID = process.env.STACK_PROJECT_ID;
|
||||
const STACK_PUBLISHABLE_CLIENT_KEY = process.env.STACK_PUBLISHABLE_CLIENT_KEY;
|
||||
|
||||
|
||||
if (!STACK_SECRET_SERVER_KEY || !STACK_PROJECT_ID || !STACK_PUBLISHABLE_CLIENT_KEY) {
|
||||
throw new Error("STACK_SECRET_SERVER_KEY, STACK_PROJECT_ID, and STACK_PUBLISHABLE_CLIENT_KEY must be set");
|
||||
}
|
||||
|
||||
export const stackServerApp = new StackServerApp({
|
||||
baseUrl: STACK_AUTH_URL,
|
||||
projectId: STACK_PROJECT_ID,
|
||||
publishableClientKey: STACK_PUBLISHABLE_CLIENT_KEY,
|
||||
secretServerKey: STACK_SECRET_SERVER_KEY,
|
||||
tokenStore: "memory",
|
||||
});
|
||||
|
||||
// Cursor only supports 40 endpoints, so we only expose the most useful tools
|
||||
const operationIDs = {
|
||||
"getUserById": ["/users/{user_id}", "get"],
|
||||
"updateUser": ["/users/{user_id}", "patch"],
|
||||
"deleteUser": ["/users/{user_id}", "delete"],
|
||||
"listUsers": ["/users", "get"],
|
||||
"createUser": ["/users", "post"],
|
||||
"listTeams": ["/teams", "get"],
|
||||
"createTeam": ["/teams", "post"],
|
||||
"listTeamMemberProfiles": ["/team-member-profiles", "get"],
|
||||
"getTeamById": ["/teams/{team_id}", "get"],
|
||||
"updateTeam": ["/teams/{team_id}", "patch"],
|
||||
"deleteTeam": ["/teams/{team_id}", "delete"],
|
||||
"addUserToTeam": ["/team-memberships/{team_id}/{user_id}", "post"],
|
||||
"removeUserFromTeam": ["/team-memberships/{team_id}/{user_id}", "delete"],
|
||||
"getTeamMemberProfile": ["/team-member-profiles/{team_id}/{user_id}", "get"],
|
||||
"updateTeamMemberProfile": ["/team-member-profiles/{team_id}/{user_id}", "patch"],
|
||||
"sendTeamInvitationCode": ["/team-invitations/send-code", "post"],
|
||||
"grantTeamUserPermission": ["/team-permissions/{team_id}/{user_id}/{permission_id}", "post"],
|
||||
"grantProjectPermission": ["/project-permissions/{user_id}/{permission_id}", "post"],
|
||||
"revokeTeamUserPermission": ["/team-permissions/{team_id}/{user_id}/{permission_id}", "delete"],
|
||||
"revokeProjectPermission": ["/project-permissions/{user_id}/{permission_id}", "delete"],
|
||||
"listTeamPermissions": ["/team-permissions", "get"],
|
||||
"getContactChannel": ["/contact-channels/{user_id}/{contact_channel_id}", "get"],
|
||||
"updateContactChannel": ["/contact-channels/{user_id}/{contact_channel_id}", "patch"],
|
||||
"deleteContactChannel": ["/contact-channels/{user_id}/{contact_channel_id}", "delete"],
|
||||
"listContactChannels": ["/contact-channels", "get"]
|
||||
};
|
||||
|
||||
function getOpenAPISchema(): OpenAPIV3_1.Document {
|
||||
return JSON.parse(readFileSync("./openapi/server.json", "utf8"));
|
||||
}
|
||||
|
||||
function isOperationObject(obj: any): obj is OpenAPIV3_1.OperationObject {
|
||||
return obj !== null && typeof obj === 'object' && 'parameters' in obj;
|
||||
}
|
||||
|
||||
|
||||
function getOperationObject(openAPISchema: OpenAPIV3_1.Document, path: string, method: string): OpenAPIV3_1.OperationObject {
|
||||
const pathItem = openAPISchema.paths?.[path];
|
||||
if (!pathItem) {
|
||||
throw new Error(`Could not find path item ${path} in openAPI schema`);
|
||||
}
|
||||
const operation = pathItem[method as keyof typeof pathItem];
|
||||
if (!operation) {
|
||||
throw new Error(`Could not find method ${method} in path item ${path} in openAPI schema`);
|
||||
}
|
||||
if (!isOperationObject(operation)) {
|
||||
throw new Error(`Method ${method} in path ${path} is not an operation object`);
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
|
||||
type ToolType = {
|
||||
path: string,
|
||||
method: string,
|
||||
name: string,
|
||||
description: string | undefined,
|
||||
inputSchema: {
|
||||
[x: string]: unknown,
|
||||
type: "object",
|
||||
properties?: {
|
||||
[x: string]: unknown,
|
||||
} | undefined,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
function getToolsFromOpenAPI(openAPISchema: OpenAPIV3_1.Document): ToolType[] {
|
||||
const tools: ToolType[] = Object.entries(operationIDs).map(([operationID, [path, method]]) => {
|
||||
const operation = getOperationObject(openAPISchema, path, method);
|
||||
const inputSchema = !operation.parameters ? {
|
||||
type: "object" as const,
|
||||
properties: {},
|
||||
required: [],
|
||||
} : convertParameterArrayToJsonSchema(operation.parameters, operation.requestBody);
|
||||
|
||||
|
||||
return {
|
||||
name: operationID,
|
||||
description: operation.description,
|
||||
inputSchema,
|
||||
path,
|
||||
method,
|
||||
};
|
||||
});
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
|
||||
async function main() {
|
||||
|
||||
const openAPISchema = getOpenAPISchema();
|
||||
const tools = getToolsFromOpenAPI(openAPISchema);
|
||||
const transport = new StdioServerTransport();
|
||||
const version = (await import("../package.json", { assert: { type: "json" } })).default.version;
|
||||
|
||||
// Create server instance
|
||||
const server = new Server({
|
||||
name: "stackauth",
|
||||
version,
|
||||
}, {
|
||||
capabilities: {
|
||||
tools: {}
|
||||
}
|
||||
});
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema,
|
||||
(): ListToolsResult => ({
|
||||
tools
|
||||
})
|
||||
);
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const name = request.params.name;
|
||||
const args = request.params.arguments ?? {};
|
||||
|
||||
const tool = tools.find(tool => tool.name === name);
|
||||
|
||||
if (!tool) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [{ type: "text", text: `Tool ${name} not found` }],
|
||||
};
|
||||
}
|
||||
|
||||
const path = tool.path;
|
||||
const method = tool.method.toUpperCase();
|
||||
|
||||
|
||||
// Split args into path and query parameters
|
||||
const queryParams = new URLSearchParams();
|
||||
const pathParams: Record<string, string> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(args)) {
|
||||
if (key.endsWith("###query")) {
|
||||
const paramName = key.replace("###query", "");
|
||||
queryParams.append(paramName, String(value));
|
||||
} else if (key.endsWith("###path")) {
|
||||
const paramName = key.replace("###path", "");
|
||||
pathParams[paramName] = String(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace path parameters
|
||||
let finalPath = path;
|
||||
for (const [key, value] of Object.entries(pathParams)) {
|
||||
finalPath = finalPath.replace(`{${key}}`, value);
|
||||
}
|
||||
|
||||
// Add query string if we have query parameters
|
||||
const queryString = queryParams.toString();
|
||||
if (queryString) {
|
||||
finalPath += `?${queryString}`;
|
||||
}
|
||||
|
||||
let body: string | undefined;
|
||||
let headers: Record<string, string> | undefined;
|
||||
// LIMITATION: we only support JSON body for now
|
||||
if (args["###body###"] && typeof args["###body###"] === "string") {
|
||||
body = args["###body###"];
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
}
|
||||
|
||||
const result = await (stackServerApp as any)[stackAppInternalsSymbol].sendRequest(finalPath, {
|
||||
method,
|
||||
headers: {
|
||||
// Hack to make api call as a server and not client, should probably create a new (internal) function for this
|
||||
"x-stack-secret-server-key": STACK_SECRET_SERVER_KEY,
|
||||
...headers,
|
||||
},
|
||||
body,
|
||||
}, "server");
|
||||
|
||||
if (!result.ok) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [{ type: "text", text: `Error: ${result.status} ${await result.text()}` }],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(await result.json(), null, 2) }],
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
|
||||
await server.connect(transport);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@ -1,155 +0,0 @@
|
||||
/**
|
||||
* Type definitions for OpenAPI Parameter Object
|
||||
*/
|
||||
import type { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
/**
|
||||
* Type definition for JSON Schema
|
||||
*/
|
||||
type JSONSchema = {
|
||||
type: 'object',
|
||||
properties: Record<string, any>,
|
||||
required?: string[],
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an object is a ReferenceObject
|
||||
* @param obj - Object to check
|
||||
* @returns True if object is a ReferenceObject, false otherwise
|
||||
*/
|
||||
function isReferenceObject(obj: any): obj is OpenAPIV3_1.ReferenceObject {
|
||||
return obj !== null && typeof obj === 'object' && '$ref' in obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts an OpenAPI Parameter Object Array to a JSON Schema
|
||||
* @param parameterObjectArray - Array of OpenAPI Parameter Objects
|
||||
* @returns JSON Schema representation of the parameters
|
||||
*/
|
||||
function convertParameterArrayToJsonSchema(parameterObjectArray: (OpenAPIV3_1.ParameterObject | OpenAPIV3_1.ReferenceObject)[], requestBody?: OpenAPIV3_1.RequestBodyObject | OpenAPIV3_1.ReferenceObject): {
|
||||
type: "object",
|
||||
properties: Record<string, any>,
|
||||
required: string[],
|
||||
} {
|
||||
if (!Array.isArray(parameterObjectArray)) {
|
||||
throw new Error('Input must be an array of parameter objects');
|
||||
}
|
||||
|
||||
|
||||
const properties = new Map<string, any>();
|
||||
const requiredParams: string[] = [];
|
||||
|
||||
parameterObjectArray.forEach(param => {
|
||||
|
||||
if (isReferenceObject(param)) {
|
||||
throw new Error('Reference objects are not supported');
|
||||
}
|
||||
if (!param.name) {
|
||||
throw new Error('Parameter object must have a name');
|
||||
}
|
||||
|
||||
const newParamName = param.name + "###" + param.in;
|
||||
|
||||
// Extract the schema from the parameter
|
||||
let schema: Record<string, any> = param.schema ?
|
||||
(typeof param.schema === 'object' ? { ...param.schema } : {}) :
|
||||
{};
|
||||
|
||||
// If the parameter has its own type/format, use those instead
|
||||
if ('type' in param && param.type) {
|
||||
schema.type = param.type;
|
||||
}
|
||||
if ('format' in param && param.format) {
|
||||
schema.format = param.format;
|
||||
}
|
||||
|
||||
// Copy description if available
|
||||
if (param.description) {
|
||||
schema.description = param.description;
|
||||
}
|
||||
|
||||
// Copy example if available
|
||||
if ('example' in param && param.example !== undefined) {
|
||||
schema.example = param.example;
|
||||
}
|
||||
|
||||
// Handle enum values
|
||||
if ('enum' in param && param.enum) {
|
||||
schema.enum = param.enum;
|
||||
}
|
||||
|
||||
// Add default value if specified
|
||||
if ('default' in param && param.default !== undefined) {
|
||||
schema.default = param.default;
|
||||
}
|
||||
|
||||
// Add parameter to properties
|
||||
properties.set(newParamName, schema);
|
||||
|
||||
// Add to required array if necessary
|
||||
if (param.required === true) {
|
||||
requiredParams.push(newParamName);
|
||||
}
|
||||
});
|
||||
|
||||
if (requestBody) {
|
||||
const body = handleRequestBody(requestBody);
|
||||
if (body) {
|
||||
properties.set("###body###", body.properties);
|
||||
requiredParams.push("###body###");
|
||||
}
|
||||
}
|
||||
|
||||
// Convert Map back to Record for return type
|
||||
const propertiesRecord: Record<string, any> = {};
|
||||
properties.forEach((value, key) => {
|
||||
propertiesRecord[key] = value;
|
||||
});
|
||||
|
||||
const jsonSchema = {
|
||||
type: 'object' as const,
|
||||
properties: propertiesRecord,
|
||||
required: requiredParams
|
||||
};
|
||||
|
||||
|
||||
return jsonSchema;
|
||||
}
|
||||
|
||||
export { convertParameterArrayToJsonSchema };
|
||||
export type { JSONSchema };
|
||||
|
||||
|
||||
function handleRequestBody(requestBody: OpenAPIV3_1.RequestBodyObject | OpenAPIV3_1.ReferenceObject) {
|
||||
|
||||
if (isReferenceObject(requestBody)) {
|
||||
throw new Error('Reference objects are not supported');
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!requestBody.content["application/json"]) {
|
||||
throw new Error('Request body must be of type application/json');
|
||||
}
|
||||
const body_schema = requestBody.content["application/json"].schema;
|
||||
if (!body_schema || isReferenceObject(body_schema)) {
|
||||
throw new Error('Reference objects are not supported');
|
||||
}
|
||||
|
||||
if (!body_schema.properties) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We simplify the body into one property, called "body"
|
||||
|
||||
return {
|
||||
type: "string",
|
||||
properties: {
|
||||
body: JSON.stringify(body_schema)
|
||||
},
|
||||
required: ["body"]
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noErrorTruncation": true,
|
||||
"outDir": "./build",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@ -26,7 +26,7 @@ COPY . .
|
||||
RUN tsx ./scripts/generate-sdks.ts
|
||||
|
||||
# https://turbo.build/repo/docs/guides/tools/docker
|
||||
RUN turbo prune --scope=@stackframe/stack-backend --scope=@stackframe/stack-dashboard --scope=@stackframe/mcp-server --docker
|
||||
RUN turbo prune --scope=@stackframe/stack-backend --scope=@stackframe/stack-dashboard --docker
|
||||
|
||||
|
||||
# Build stage
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
"restart-dev-environment": "pnpm pre && concurrently \"pnpm run build:packages && pnpm run codegen\" \"pnpm run restart-deps\" && pnpm run restart-dev-in-background",
|
||||
"stop-dev-environment": "pnpm pre && pnpm run kill-dev:named && pnpm run stop-deps",
|
||||
"clean": "pnpm pre-no-codegen && turbo run clean && rimraf --glob **/.next && rimraf --glob **/.turbo && rimraf .turbo && rimraf --glob **/node_modules",
|
||||
"codegen": "pnpm pre && turbo run codegen && pnpm run generate-sdks && pnpm run generate-openapi && pnpm run generate-openapi-fumadocs",
|
||||
"codegen": "pnpm pre && turbo run codegen && pnpm run generate-sdks && pnpm run generate-openapi-fumadocs",
|
||||
"deps-compose": "docker compose -p stack-dependencies -f docker/dependencies/docker.compose.yaml",
|
||||
"stop-deps": "POSTGRES_DELAY_MS=0 pnpm run deps-compose kill && POSTGRES_DELAY_MS=0 pnpm run deps-compose down -v",
|
||||
"wait-until-postgres-is-ready:pg_isready": "until pg_isready -h localhost -p 5432; do sleep 1; done",
|
||||
@ -58,7 +58,6 @@
|
||||
"test:e2e": "pnpm pre && vitest e2e/tests",
|
||||
"test:unit": "pnpm pre && vitest src",
|
||||
"verify-data-integrity": "pnpm pre && pnpm -C apps/backend run verify-data-integrity",
|
||||
"generate-openapi": "pnpm pre && turbo run generate-openapi",
|
||||
"generate-openapi-fumadocs": "turbo run generate-openapi-fumadocs",
|
||||
"generate-keys": "pnpm pre && turbo run generate-keys",
|
||||
"generate-sdks": "npx --package=tsx tsx ./scripts/generate-sdks.ts",
|
||||
|
||||
@ -59,11 +59,6 @@
|
||||
],
|
||||
"outputLogs": "new-only"
|
||||
},
|
||||
"@stackframe/mcp-server#build": {
|
||||
"dependsOn": [
|
||||
"@stackframe/stack-backend#build"
|
||||
]
|
||||
},
|
||||
"@stackframe/stack-backend#build": {
|
||||
"dependsOn": [
|
||||
"codegen"
|
||||
@ -97,9 +92,6 @@
|
||||
"typecheck": {
|
||||
"dependsOn": []
|
||||
},
|
||||
"generate-openapi": {
|
||||
"cache": false
|
||||
},
|
||||
"generate-keys": {
|
||||
"cache": false
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user