Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
66af47d
Enhance documentation tools integration
mantrakp04 Mar 23, 2026
30d53e8
Merge branch 'dev' into dario-likes-mcps
mantrakp04 Mar 23, 2026
5078747
Enhance error handling and API response for documentation tools
mantrakp04 Mar 24, 2026
aaf49db
Merge branch 'dev' into dario-likes-mcps
mantrakp04 Mar 24, 2026
844e916
Refactor askStackAuth key to ask_stack_auth in API documentation
mantrakp04 Mar 24, 2026
274c742
fix: register private submodule gitlink in the index
mantrakp04 Mar 25, 2026
c7a3cca
Merge branch 'dev' into dario-likes-mcps
mantrakp04 Mar 25, 2026
ef2289f
Merge branch 'dev' into dario-likes-mcps
mantrakp04 Apr 3, 2026
d8065c4
Update environment configurations and remove internal secret validati…
mantrakp04 Apr 4, 2026
3b27eee
Merge branch 'dev' into dario-likes-mcps
mantrakp04 Apr 6, 2026
b82efa4
Merge branch 'dev' into dario-likes-mcps
mantrakp04 Apr 6, 2026
158498b
Merge branch 'dev' into dario-likes-mcps
mantrakp04 Apr 8, 2026
b22d4b0
Merge branch 'dev' into dario-likes-mcps
mantrakp04 Apr 9, 2026
95ca0a2
initial commit
aadesh18 Apr 10, 2026
fbab066
Merge remote-tracking branch 'origin/dario-likes-mcps' into llm-mcp-flow
aadesh18 Apr 10, 2026
73152a1
pnpm lock
aadesh18 Apr 10, 2026
e16040c
changed port
aadesh18 Apr 10, 2026
a07dbab
spacetime db ci change
aadesh18 Apr 10, 2026
ef77edc
ci fix
aadesh18 Apr 10, 2026
84dffa2
security fix
aadesh18 Apr 10, 2026
a0486e9
security fixes
aadesh18 Apr 11, 2026
8c596ec
Merge branch 'dev' into dario-likes-mcps
mantrakp04 Apr 12, 2026
ef6963d
Merge branch 'dev' into dario-likes-mcps
N2D4 Apr 12, 2026
1c69185
Merge branch 'dario-likes-mcps' into llm-mcp-flow
aadesh18 Apr 12, 2026
f794bd6
Merge remote-tracking branch 'origin/dev' into llm-mcp-flow
aadesh18 Apr 12, 2026
59a060a
merge error
aadesh18 Apr 13, 2026
0485c73
pr comment changes
aadesh18 Apr 13, 2026
97ee052
Merge branch 'dev' into llm-mcp-flow
aadesh18 Apr 13, 2026
411f775
bug fix
aadesh18 Apr 13, 2026
c514efd
Merge branch 'llm-mcp-flow' of https://github.com/stack-auth/stack-au…
aadesh18 Apr 13, 2026
516c424
Merge branch 'dev' into llm-mcp-flow
aadesh18 Apr 13, 2026
b0e3341
pr comments
aadesh18 Apr 13, 2026
a630be1
Merge branch 'llm-mcp-flow' of https://github.com/stack-auth/stack-au…
aadesh18 Apr 13, 2026
8c7bc54
tests failing
aadesh18 Apr 13, 2026
7a54be9
comment changes
aadesh18 Apr 13, 2026
bd3925d
Merge branch 'dev' into llm-mcp-flow
aadesh18 Apr 13, 2026
ca461d4
tests fix
aadesh18 Apr 13, 2026
224468c
Merge branch 'llm-mcp-flow' of https://github.com/stack-auth/stack-au…
aadesh18 Apr 13, 2026
042e616
tests fix
aadesh18 Apr 13, 2026
149d6d7
fixed the order
aadesh18 Apr 13, 2026
574cc4a
Merge branch 'dev' into llm-mcp-flow
aadesh18 Apr 13, 2026
3293845
Merge branch 'dev' into llm-mcp-flow
aadesh18 Apr 13, 2026
d8e99d6
Merge branch 'dev' into llm-mcp-flow
aadesh18 Apr 14, 2026
fa4c814
Merge branch 'dev' into llm-mcp-flow
aadesh18 Apr 14, 2026
a4c3306
pr changes
aadesh18 Apr 14, 2026
35739af
Merge branch 'dev' into llm-mcp-flow
aadesh18 Apr 14, 2026
15e5879
Merge remote-tracking branch 'origin/dev' into llm-mcp-flow
aadesh18 Apr 15, 2026
140ee7e
Merge branch 'dev' into llm-mcp-flow
aadesh18 Apr 15, 2026
afd84bc
minor fix
aadesh18 Apr 15, 2026
30b4e49
Merge remote-tracking branch 'origin/dev' into llm-mcp-flow
aadesh18 Apr 15, 2026
bc1550e
merge conflicts fixed
aadesh18 Apr 15, 2026
75a2786
Merge branch 'dev' into llm-mcp-flow
aadesh18 Apr 15, 2026
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
6 changes: 6 additions & 0 deletions .github/workflows/db-migration-backwards-compatibility.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ jobs:
- name: Create .env.test.local file for examples/convex
run: cp examples/convex/.env.development examples/convex/.env.test.local

- name: Create .env.test.local file for apps/internal-tool
run: cp apps/internal-tool/.env.development apps/internal-tool/.env.test.local

- name: Build
run: pnpm build

Expand Down Expand Up @@ -332,6 +335,9 @@ jobs:
- name: Create .env.test.local file for examples/convex
run: cp examples/convex/.env.development examples/convex/.env.test.local

- name: Create .env.test.local file for apps/internal-tool
run: cp apps/internal-tool/.env.development apps/internal-tool/.env.test.local

- name: Build
run: pnpm build

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/e2e-api-tests-local-emulator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ jobs:
- name: Create .env.test.local file for examples/convex
run: cp examples/convex/.env.development examples/convex/.env.test.local

- name: Create .env.test.local file for apps/internal-tool
run: cp apps/internal-tool/.env.development apps/internal-tool/.env.test.local

- name: Build
run: pnpm build

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/e2e-api-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ jobs:
- name: Create .env.test.local file for examples/convex
run: cp examples/convex/.env.development examples/convex/.env.test.local

- name: Create .env.test.local file for apps/internal-tool
run: cp apps/internal-tool/.env.development apps/internal-tool/.env.test.local

- name: Build
run: pnpm build

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/e2e-custom-base-port-api-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ jobs:
- name: Create .env.test.local file for examples/convex
run: cp examples/convex/.env.development examples/convex/.env.test.local

- name: Create .env.test.local file for apps/internal-tool
run: cp apps/internal-tool/.env.development apps/internal-tool/.env.test.local

- name: Build
run: pnpm build

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/e2e-fallback-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jobs:
cp examples/middleware/.env.development examples/middleware/.env.test.local
cp examples/supabase/.env.development examples/supabase/.env.test.local
cp examples/convex/.env.development examples/convex/.env.test.local
cp apps/internal-tool/.env.development apps/internal-tool/.env.test.local

- name: Build
run: pnpm build
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/lint-and-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ jobs:
- name: Create .env.production.local file for examples/convex
run: cp examples/convex/.env.development examples/convex/.env.production.local

- name: Create .env.production.local file for apps/internal-tool
run: cp apps/internal-tool/.env.development apps/internal-tool/.env.production.local

- name: Build
run: pnpm build

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[![Stack Logo](/.github/assets/logo.png)](https://stack-auth.com)

[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/stack-auth/stack-auth)

<h3 align="center">
<a href="https://docs.stack-auth.com">📘 Docs</a>
| <a href="https://stack-auth.com/">☁️ Hosted Version</a>
Expand Down
5 changes: 5 additions & 0 deletions apps/backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,8 @@ STACK_TELEGRAM_CHAT_ID=# enter your telegram chat id

# Docs AI tool bundle
STACK_DOCS_INTERNAL_BASE_URL=# override the docs origin used by the backend's AI tool bundle to call the docs app's `/api/internal/docs-tools` endpoint. Defaults to http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}04 in dev, https://mcp.stack-auth.com in prod

# MCP review tool (SpacetimeDB)
STACK_SPACETIMEDB_URI=# SpacetimeDB host URI; default empty (logging disabled)
STACK_SPACETIMEDB_DB_NAME=# SpacetimeDB database name
STACK_MCP_LOG_TOKEN=# shared secret gating the log_mcp_call reducer; must match EXPECTED_LOG_TOKEN in apps/internal-tool/spacetimedb/src/index.ts
5 changes: 5 additions & 0 deletions apps/backend/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ STACK_QSTASH_TOKEN=eyJVc2VySUQiOiJkZWZhdWx0VXNlciIsIlBhc3N3b3JkIjoiZGVmYXVsdFBhc
STACK_QSTASH_CURRENT_SIGNING_KEY=sig_7kYjw48mhY7kAjqNGcy6cr29RJ6r
STACK_QSTASH_NEXT_SIGNING_KEY=sig_5ZB6DVzB1wjE8S6rZ7eenA8Pdnhs

# MCP review tool (SpacetimeDB)
STACK_SPACETIMEDB_URI=ws://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}39
STACK_SPACETIMEDB_DB_NAME=stack-auth-llm
STACK_MCP_LOG_TOKEN=change-me

# Clickhouse
STACK_CLICKHOUSE_URL=http://localhost:${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}36
STACK_CLICKHOUSE_ADMIN_USER=stackframe
Expand Down
1 change: 1 addition & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
},
"dependencies": {
"@ai-sdk/mcp": "^1.0.21",
"spacetimedb": "^2.1.0",
"@ai-sdk/openai": "^3.0.29",
"@aws-sdk/client-s3": "^3.855.0",
"@clickhouse/client": "^1.14.0",
Expand Down
59 changes: 52 additions & 7 deletions apps/backend/src/app/api/latest/ai/query/[mode]/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { logMcpCall } from "@/lib/ai/mcp-logger";
import { selectModel } from "@/lib/ai/models";
import { getFullSystemPrompt } from "@/lib/ai/prompts";
import { reviewMcpCall } from "@/lib/ai/qa-reviewer";
import { requestBodySchema } from "@/lib/ai/schema";
import { getTools, validateToolNames } from "@/lib/ai/tools";
import { getVerifiedQaContext } from "@/lib/ai/verified-qa";
import { listManagedProjectIds } from "@/lib/projects";
import { SmartResponse } from "@/route-handlers/smart-response";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { runAsynchronouslyAndWaitUntil } from "@/utils/background-tasks";
import { validateImageAttachments } from "@stackframe/stack-shared/dist/ai/image-limits";
import { yupMixed, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
Expand Down Expand Up @@ -52,15 +56,14 @@ export const POST = createSmartRouteHandler({
}

const model = selectModel(quality, speed, isAuthenticated);
const systemPrompt = getFullSystemPrompt(systemPromptId);
const isDocsOrSearch = systemPromptId === "docs-ask-ai" || systemPromptId === "command-center-ask-ai";
let systemPrompt = getFullSystemPrompt(systemPromptId);
if (isDocsOrSearch) {
systemPrompt += await getVerifiedQaContext();
}
const tools = await getTools(toolNames, { auth: fullReq.auth, targetProjectId: projectId });
const toolsArg = Object.keys(tools).length > 0 ? tools : undefined;
const isDocsOrSearch = systemPromptId === "docs-ask-ai" || systemPromptId === "command-center-ask-ai";
// create-dashboard now does an inspection loop (queryAnalytics) before calling updateDashboard,
// so it needs room for ~3 exploratory queries + the final tool call + some retry slack.
const isCreateDashboard = systemPromptId === "create-dashboard";
// build-analytics-query aims for one-shot queries with complete schema
// knowledge, but needs a few steps for retries on errors or follow-ups.
const isBuildAnalyticsQuery = systemPromptId === "build-analytics-query";
const stepLimit = toolsArg == null
? 1
Expand All @@ -86,6 +89,7 @@ export const POST = createSmartRouteHandler({
body: result.toUIMessageStreamResponse(),
};
} else {
const startedAt = Date.now();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 120_000);
const result = await generateText({
Expand Down Expand Up @@ -134,10 +138,51 @@ export const POST = createSmartRouteHandler({
});
});

let responseConversationId: string | undefined;
if (body.mcpCallMetadata != null) {
const correlationId = crypto.randomUUID();
const conversationId = body.mcpCallMetadata.conversationId ?? crypto.randomUUID();
responseConversationId = conversationId;
const firstUserMessage = messages.find(m => m.role === "user");
const question = typeof firstUserMessage?.content === "string"
? firstUserMessage.content
: JSON.stringify(firstUserMessage?.content ?? "");

const innerToolCallsJson = JSON.stringify(contentBlocks.filter(b => b.type === "tool-call"));

const logPromise = logMcpCall({
correlationId,
toolName: body.mcpCallMetadata.toolName,
reason: body.mcpCallMetadata.reason,
userPrompt: body.mcpCallMetadata.userPrompt,
conversationId,
question,
response: result.text,
stepCount: result.steps.length,
innerToolCallsJson,
durationMs: BigInt(Date.now() - startedAt),
modelId: String(model.modelId),
errorMessage: undefined,
});
runAsynchronouslyAndWaitUntil(logPromise);

runAsynchronouslyAndWaitUntil(reviewMcpCall({
logPromise,
correlationId,
question,
reason: body.mcpCallMetadata.reason,
response: result.text,
}));
}

return {
statusCode: 200,
bodyType: "json" as const,
body: { content: contentBlocks, finalText: result.text },
body: {
content: contentBlocks,
finalText: result.text,
conversationId: responseConversationId ?? null,
},
};
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { getConnectionOrThrow } from "@/lib/ai/mcp-logger";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { adaptSchema, yupBoolean, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { getEnvVariable, getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";

export const POST = createSmartRouteHandler({
metadata: { hidden: true },
request: yupObject({
auth: yupObject({
type: adaptSchema,
user: adaptSchema.defined(),
project: adaptSchema,
}).defined(),
body: yupObject({
question: yupString().defined(),
answer: yupString().defined(),
publish: yupBoolean().defined(),
}).defined(),
method: yupString().oneOf(["POST"]).defined(),
}),
response: yupObject({
statusCode: yupNumber().oneOf([200]).defined(),
bodyType: yupString().oneOf(["json"]).defined(),
body: yupObject({
success: yupBoolean().defined(),
}).defined(),
}),
handler: async ({ auth, body }) => {
const user = auth.user;
if (getNodeEnvironment() !== "development") {
const metadata = user.client_read_only_metadata;
if (!(metadata && typeof metadata === "object" && "isAiChatReviewer" in metadata && metadata.isAiChatReviewer === true)) {
throw new StatusError(StatusError.Forbidden, "You are not approved to perform MCP review operations.");
}
}

const conn = await getConnectionOrThrow();

const token = getEnvVariable("STACK_MCP_LOG_TOKEN");
await conn.reducers.addManualQa({
token,
question: body.question,
answer: body.answer,
publish: body.publish,
reviewedBy: user.display_name ?? user.primary_email ?? user.id,
});

return {
statusCode: 200,
bodyType: "json" as const,
body: { success: true },
};
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { getConnectionOrThrow } from "@/lib/ai/mcp-logger";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { adaptSchema, yupBoolean, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { getEnvVariable, getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";

export const POST = createSmartRouteHandler({
metadata: { hidden: true },
request: yupObject({
auth: yupObject({
type: adaptSchema,
user: adaptSchema.defined(),
project: adaptSchema,
}).defined(),
body: yupObject({
correlationId: yupString().defined(),
}).defined(),
method: yupString().oneOf(["POST"]).defined(),
}),
response: yupObject({
statusCode: yupNumber().oneOf([200]).defined(),
bodyType: yupString().oneOf(["json"]).defined(),
body: yupObject({
success: yupBoolean().defined(),
}).defined(),
}),
handler: async ({ auth, body }) => {
if (getNodeEnvironment() !== "development") {
const metadata = auth.user.client_read_only_metadata;
if (!(metadata && typeof metadata === "object" && "isAiChatReviewer" in metadata && metadata.isAiChatReviewer === true)) {
throw new StatusError(StatusError.Forbidden, "You are not approved to perform MCP review operations.");
}
}

const conn = await getConnectionOrThrow();

const token = getEnvVariable("STACK_MCP_LOG_TOKEN");
await conn.reducers.deleteQaEntry({
token,
correlationId: body.correlationId,
});

return {
statusCode: 200,
bodyType: "json" as const,
body: { success: true },
};
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { getConnectionOrThrow } from "@/lib/ai/mcp-logger";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { adaptSchema, yupBoolean, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { getEnvVariable, getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";

export const POST = createSmartRouteHandler({
metadata: { hidden: true },
request: yupObject({
auth: yupObject({
type: adaptSchema,
user: adaptSchema.defined(),
project: adaptSchema,
}).defined(),
body: yupObject({
correlationId: yupString().defined(),
}).defined(),
method: yupString().oneOf(["POST"]).defined(),
}),
response: yupObject({
statusCode: yupNumber().oneOf([200]).defined(),
bodyType: yupString().oneOf(["json"]).defined(),
body: yupObject({
success: yupBoolean().defined(),
}).defined(),
}),
handler: async ({ auth, body }) => {
const user = auth.user;
if (getNodeEnvironment() !== "development") {
const metadata = user.client_read_only_metadata;
if (!(metadata && typeof metadata === "object" && "isAiChatReviewer" in metadata && metadata.isAiChatReviewer === true)) {
throw new StatusError(StatusError.Forbidden, "You are not approved to perform MCP review operations.");
}
}

const conn = await getConnectionOrThrow();

const token = getEnvVariable("STACK_MCP_LOG_TOKEN");
await conn.reducers.markHumanReviewed({
token,
correlationId: body.correlationId,
reviewedBy: user.display_name ?? user.primary_email ?? user.id,
});

return {
statusCode: 200,
bodyType: "json" as const,
body: { success: true },
};
},
});
Loading
Loading