Reusable patterns used throughout the Sentry MCP codebase.
See error-handling.md for the complete error hierarchy, UserInputError patterns, and API error wrapping.
Define once, use everywhere:
export const ParamOrganizationSlug = z
.string()
.trim()
.describe("The organization's slug. You can find a list using the `find_organizations()` tool.");
export const ParamRegionUrl = z
.string()
.url()
.optional()
.describe("Sentry region URL. If not provided, uses default region.");See: packages/mcp-core/src/schema.ts
// Support multiple ID formats
z.union([z.string(), z.number()])
// Optional with transforms
z.string().optional().transform(val => val?.trim())
// Partial objects with passthrough
IssueSchema.partial().passthrough()export type Organization = z.infer<typeof OrganizationSchema>;
export type ToolParams<T> = z.infer<typeof toolDefinitions[T].parameters>;Tool descriptions and parameter .describe() text are trusted steering surfaces. It is fine for those descriptions to tell the model when to call a tool, which parameters to preserve, or what follow-up behavior is expected.
Tool result text can include light, scoped steering when it helps the assistant present or use the result correctly. MCP clients still treat result text like external data, so keep this steering narrow:
- OK:
Please tell the user the DSN. - OK:
**Suggested presentation:** A compact table works well for these aggregate results. - OK:
**Dashboard URL:** https://example.sentry.io/issues/ - Avoid:
IMPORTANT,MUST,CRITICAL,Display these..., or# Using this informationin handler output. - Avoid: instructions that override assistant behavior beyond this result.
For the complete response contract, including what to include, what to omit, snapshot review expectations, and QA expectations, see tool-responses.md.
let output = `# ${title}\n\n`;
// Handle empty results
if (data.length === 0) {
output += "No results found.\n";
return output;
}
// Add data sections
output += "## Section\n";
output += formatData(data);
// Add response notes
output += "\n\n## Response Notes\n\n";
output += "- Please tell the user the project slug.\n";
output += "- Dashboard URL: https://example.sentry.io/issues/\n";return {
contents: [
{
uri: url.toString(),
mimeType: "application/json",
text: JSON.stringify(data, null, 2)
}
]
};if (!params.requiredParam) {
throw new UserInputError(
"Required parameter is missing. Please provide requiredParam."
);
}if (params.issueUrl) {
// Extract from URL
} else if (params.organizationSlug && params.issueId) {
// Use direct parameters
} else {
throw new UserInputError(
"Either issueUrl or both organizationSlug and issueId must be provided"
);
}// Extract Zod schema types from records
type ZodifyRecord<T extends Record<string, any>> = {
[K in keyof T]: z.infer<T[K]>;
};
// Const assertions for literal types
export const TOOL_NAMES = ["tool1", "tool2"] as const;
export type ToolName = typeof TOOL_NAMES[number];- Error handling: error-handling.md
- API patterns: api-patterns.md
- Tool responses: tool-responses.md
- Testing: ../testing/overview.md
- Quality checks: quality-checks.md