Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions packages/mcp-cloudflare/src/server/lib/mcp-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import type { ServerContext } from "@sentry/mcp-server/types";
import type { Env } from "../types";
import { verifyConstraintsAccess } from "./constraint-utils";
import type { ExportedHandler } from "@cloudflare/workers-types";
import agentTools from "@sentry/mcp-server/tools/agent-tools";

/**
* ExecutionContext with OAuth props injected by the OAuth provider.
Expand Down Expand Up @@ -64,6 +63,7 @@ const mcpHandler: ExportedHandler<Env> = {

// Extract OAuth props from ExecutionContext (set by OAuth provider)
const oauthCtx = ctx as OAuthExecutionContext;

if (!oauthCtx.props) {
throw new Error("No authentication context available");
}
Expand Down Expand Up @@ -167,7 +167,7 @@ const mcpHandler: ExportedHandler<Env> = {
// Context is captured in tool handler closures during buildServer()
const server = buildServer({
context: serverContext,
tools: isAgentMode ? agentTools : undefined,
agentMode: isAgentMode,
});

// Run MCP handler - context already captured in closures
Expand Down
5 changes: 2 additions & 3 deletions packages/mcp-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { ALL_SCOPES } from "./permissions";
import { DEFAULT_SCOPES, DEFAULT_SKILLS } from "./constants";
import { SKILLS } from "./skills";
import { setOpenAIBaseUrl } from "./internal/agents/openai-provider";
import agentTools from "./tools/agent-tools";

const packageName = "@sentry/mcp-server";
const allSkills = Object.keys(SKILLS) as ReadonlyArray<
Expand Down Expand Up @@ -142,10 +141,10 @@ const context = {
};

// Build server with context to filter tools based on granted scopes
// Use agentTools when --agent flag is set (only exposes use_sentry tool)
// Use agentMode when --agent flag is set (only exposes use_sentry tool)
const server = buildServer({
context,
tools: cli.agent ? agentTools : undefined,
agentMode: cli.agent,
});

startStdio(server, context).catch((err) => {
Expand Down
29 changes: 25 additions & 4 deletions packages/mcp-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,26 @@ function extractMcpParameters(args: Record<string, unknown>) {
export function buildServer({
context,
onToolComplete,
agentMode = false,
tools: customTools,
}: {
context: ServerContext;
onToolComplete?: () => void;
agentMode?: boolean;
tools?: Record<string, ToolConfig<any>>;
}): McpServer {
const server = new McpServer({
name: MCP_SERVER_NAME,
version: LIB_VERSION,
});

configureServer({ server, context, onToolComplete, tools: customTools });
configureServer({
server,
context,
onToolComplete,
agentMode,
tools: customTools,
});

return wrapMcpServerWithSentry(server);
}
Expand All @@ -124,20 +132,29 @@ export function buildServer({
* Internal function used by buildServer(). Use buildServer() instead for most cases.
* Tools are filtered at registration time based on grantedSkills OR grantedScopes
* (either system can grant access), and context is captured in closures for tool handler execution.
*
* In agent mode, only the use_sentry tool is registered, bypassing authorization checks.
*/
function configureServer({
server,
context,
onToolComplete,
agentMode = false,
tools: customTools,
}: {
server: McpServer;
context: ServerContext;
onToolComplete?: () => void;
agentMode?: boolean;
tools?: Record<string, ToolConfig<any>>;
}) {
// Use custom tools if provided, otherwise use default tools
const toolsToRegister = customTools ?? tools;
// Determine which tools to register:
// - Agent mode: only use_sentry
// - Custom tools provided: use those
// - Default: all standard tools
const toolsToRegister = agentMode
? { use_sentry: tools.use_sentry }
: (customTools ?? tools);

// Get granted skills and scopes from context for tool filtering
const grantedSkills: Set<Skill> | undefined = context.grantedSkills
Expand Down Expand Up @@ -201,8 +218,12 @@ function configureServer({
*/
let allowed = false;

// In agent mode, skip authorization - use_sentry handles it internally
if (agentMode) {
allowed = true;
}
// Skills system takes precedence when set
if (grantedSkills) {
else if (grantedSkills) {
// Tool must have non-empty requiredSkills to be exposed in skills mode
if (tool.requiredSkills && tool.requiredSkills.length > 0) {
allowed = hasRequiredSkills(grantedSkills, tool.requiredSkills);
Expand Down
15 changes: 0 additions & 15 deletions packages/mcp-server/src/tools/agent-tools.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/mcp-server/src/tools/use-sentry/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function formatToolCallTrace(toolCalls: ToolCall[]): string {

export default defineTool({
name: "use_sentry",
requiredSkills: [], // Not exposed via standard MCP - accessed via agent mode
requiredSkills: [], // Only available in agent mode - bypasses authorization
requiredScopes: [], // No specific scopes - uses authentication token
description: [
"Use Sentry's MCP Agent to answer questions related to Sentry (sentry.io).",
Expand Down Expand Up @@ -68,7 +68,7 @@ export default defineTool({
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();

// Filter out use_sentry from tools to prevent recursion and circular dependency
// Exclude use_sentry from tools to prevent recursion
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { use_sentry, ...toolsForAgent } = tools;

Expand Down