@securityreviewai/security-review-mcp 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,229 @@
1
+ import * as z from "zod/v4";
2
+ export function registerGenerationPrompts(server) {
3
+ server.registerPrompt("document_generation", {
4
+ description: "Generate a detailed architecture/design document from minimal user input. The generated document is suitable for uploading to SRAI as a knowledge base document for security review. Use when the user wants to create an SRAI project from a brief description.",
5
+ argsSchema: {
6
+ system_name: z.string(),
7
+ brief_description: z.string(),
8
+ doc_type: z.string().default("architecture"),
9
+ },
10
+ }, async ({ system_name, brief_description, doc_type }) => {
11
+ const normalized = doc_type.toLowerCase();
12
+ if (normalized === "api") {
13
+ return promptText(apiTemplate(system_name, brief_description));
14
+ }
15
+ if (normalized === "deployment") {
16
+ return promptText(deploymentTemplate(system_name, brief_description));
17
+ }
18
+ return promptText(architectureTemplate(system_name, brief_description));
19
+ });
20
+ }
21
+ function architectureTemplate(systemName, briefDescription) {
22
+ return `You are a senior software architect. Generate a comprehensive architecture document for the system described below. The document should be detailed enough for a security team to perform a thorough threat model analysis.
23
+
24
+ ## System: ${systemName}
25
+
26
+ ## Brief Description
27
+ ${briefDescription}
28
+
29
+ ## INSTRUCTIONS
30
+
31
+ Generate a detailed **Architecture Design Document** that includes ALL of the following sections. Be thorough and specific -- infer reasonable architectural decisions from the brief description.
32
+
33
+ ### Document Structure
34
+
35
+ **1. Executive Summary**
36
+ - System purpose and business context
37
+ - Key stakeholders
38
+ - High-level architecture style (monolith, microservices, serverless, etc.)
39
+
40
+ **2. System Overview**
41
+ - Detailed functional description
42
+ - Key business capabilities
43
+ - User types and their roles
44
+ - System boundaries
45
+
46
+ **3. Architecture Components**
47
+ For each component, describe:
48
+ - Name and purpose
49
+ - Technology stack (language, framework, database, etc.)
50
+ - APIs exposed (endpoints, protocols)
51
+ - Data stored or processed
52
+ - Authentication/authorization mechanisms
53
+ - Scaling characteristics
54
+
55
+ **4. Data Architecture**
56
+ - Data models (key entities and relationships)
57
+ - Data stores (databases, caches, file storage)
58
+ - Data classification (what is sensitive, PII, financial, etc.)
59
+ - Data flow between components
60
+ - Data retention and lifecycle
61
+
62
+ **5. Integration Architecture**
63
+ - External services and APIs consumed
64
+ - Third-party integrations
65
+ - Message queues or event buses
66
+ - Webhooks and callbacks
67
+
68
+ **6. Infrastructure & Deployment**
69
+ - Cloud provider and services used
70
+ - Network topology (VPCs, subnets, security groups)
71
+ - Container orchestration (if applicable)
72
+ - CI/CD pipeline
73
+ - Environment strategy (dev, staging, production)
74
+
75
+ **7. Security Architecture**
76
+ - Authentication mechanism (OAuth2, JWT, SAML, etc.)
77
+ - Authorization model (RBAC, ABAC, etc.)
78
+ - Encryption at rest and in transit
79
+ - Secret management approach
80
+ - Logging and monitoring
81
+ - Network security controls
82
+
83
+ **8. Non-Functional Requirements**
84
+ - Performance targets
85
+ - Availability/SLA requirements
86
+ - Compliance requirements
87
+ - Scalability approach
88
+
89
+ Format the document with clear markdown headers, bullet points, and tables where appropriate. Make it comprehensive but readable. This document will be used for security threat modeling, so err on the side of more detail about data flows, trust boundaries, and security-relevant decisions.`;
90
+ }
91
+ function apiTemplate(systemName, briefDescription) {
92
+ return `You are a senior API architect. Generate a comprehensive API specification document for the system described below. The document should be detailed enough for a security team to assess the API attack surface.
93
+
94
+ ## System: ${systemName}
95
+
96
+ ## Brief Description
97
+ ${briefDescription}
98
+
99
+ ## INSTRUCTIONS
100
+
101
+ Generate a detailed **API Design Document** that includes ALL of the following sections:
102
+
103
+ **1. API Overview**
104
+ - Base URL structure
105
+ - API versioning strategy
106
+ - Authentication mechanism
107
+ - Rate limiting policy
108
+
109
+ **2. Authentication & Authorization**
110
+ - Auth flows (OAuth2, API keys, JWT, etc.)
111
+ - Token lifecycle (issuance, refresh, revocation)
112
+ - Permission model
113
+ - Scopes or roles
114
+
115
+ **3. API Endpoints**
116
+ For each major endpoint group, document:
117
+ - HTTP method and path
118
+ - Request parameters (path, query, body)
119
+ - Request/response schemas
120
+ - Authentication requirements
121
+ - Rate limits
122
+ - Error responses
123
+
124
+ **4. Data Models**
125
+ - Key request/response objects
126
+ - Sensitive fields and their handling
127
+ - Validation rules
128
+
129
+ **5. Security Controls**
130
+ - Input validation approach
131
+ - Output encoding
132
+ - CORS policy
133
+ - Security headers
134
+ - Request size limits
135
+ - File upload handling
136
+
137
+ **6. Error Handling**
138
+ - Error response format
139
+ - Error codes and their meanings
140
+ - Information leakage prevention
141
+
142
+ **7. Integration Patterns**
143
+ - Webhooks and callbacks
144
+ - Pagination strategy
145
+ - Filtering and sorting
146
+ - Batch operations
147
+
148
+ Format as a detailed technical document suitable for security threat modeling.`;
149
+ }
150
+ function deploymentTemplate(systemName, briefDescription) {
151
+ return `You are a senior DevOps/platform engineer. Generate a comprehensive deployment and infrastructure document for the system described below. The document should be detailed enough for a security team to assess the infrastructure attack surface.
152
+
153
+ ## System: ${systemName}
154
+
155
+ ## Brief Description
156
+ ${briefDescription}
157
+
158
+ ## INSTRUCTIONS
159
+
160
+ Generate a detailed **Deployment & Infrastructure Document** including:
161
+
162
+ **1. Infrastructure Overview**
163
+ - Cloud provider(s) and regions
164
+ - Architecture style (containers, VMs, serverless)
165
+ - High availability design
166
+
167
+ **2. Network Architecture**
168
+ - VPC/network design
169
+ - Subnet layout (public, private, data)
170
+ - Load balancers and CDN
171
+ - DNS configuration
172
+ - Firewall rules and security groups
173
+ - Network segmentation
174
+
175
+ **3. Compute Resources**
176
+ - Application servers / containers
177
+ - Container orchestration (Kubernetes, ECS, etc.)
178
+ - Auto-scaling policies
179
+ - Resource limits
180
+
181
+ **4. Data Infrastructure**
182
+ - Database servers and configuration
183
+ - Caching layer
184
+ - Object storage
185
+ - Backup and recovery strategy
186
+
187
+ **5. CI/CD Pipeline**
188
+ - Source code management
189
+ - Build process
190
+ - Testing stages
191
+ - Deployment strategy (blue-green, canary, rolling)
192
+ - Artifact management
193
+
194
+ **6. Security Infrastructure**
195
+ - Identity and access management
196
+ - Secret management (Vault, AWS Secrets Manager, etc.)
197
+ - Certificate management
198
+ - WAF and DDoS protection
199
+ - Vulnerability scanning
200
+
201
+ **7. Observability**
202
+ - Logging infrastructure
203
+ - Metrics collection
204
+ - Alerting rules
205
+ - Distributed tracing
206
+ - Incident response procedures
207
+
208
+ **8. Compliance**
209
+ - Encryption at rest configuration
210
+ - Encryption in transit configuration
211
+ - Audit logging
212
+ - Data residency
213
+ - Compliance certifications
214
+
215
+ Format as a detailed technical document suitable for security threat modeling.`;
216
+ }
217
+ function promptText(text) {
218
+ return {
219
+ messages: [
220
+ {
221
+ role: "user",
222
+ content: {
223
+ type: "text",
224
+ text,
225
+ },
226
+ },
227
+ ],
228
+ };
229
+ }
@@ -0,0 +1,101 @@
1
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { getApiClient, SraiApiError } from "../api/client.js";
3
+ import { jsonStringify } from "../utils/serialization.js";
4
+ export function registerReviewResources(server) {
5
+ server.registerResource("SRAI Projects", "srai://projects", {
6
+ description: "List of all projects in the SRAI platform.",
7
+ mimeType: "application/json",
8
+ }, async (uri) => {
9
+ try {
10
+ const result = await getApiClient().listProjects();
11
+ return {
12
+ contents: [
13
+ {
14
+ uri: uri.href,
15
+ text: jsonStringify(result),
16
+ mimeType: "application/json",
17
+ },
18
+ ],
19
+ };
20
+ }
21
+ catch (error) {
22
+ return resourceError(uri, error);
23
+ }
24
+ });
25
+ server.registerResource("Project Reviews", new ResourceTemplate("srai://projects/{project_id}/reviews", { list: undefined }), {
26
+ description: "List of all reviews for a specific SRAI project.",
27
+ mimeType: "application/json",
28
+ }, async (uri, variables) => {
29
+ try {
30
+ const projectId = Number(variables.project_id);
31
+ const result = await getApiClient().listReviews(projectId);
32
+ return {
33
+ contents: [
34
+ {
35
+ uri: uri.href,
36
+ text: jsonStringify(result),
37
+ mimeType: "application/json",
38
+ },
39
+ ],
40
+ };
41
+ }
42
+ catch (error) {
43
+ return resourceError(uri, error);
44
+ }
45
+ });
46
+ server.registerResource("Review Summary", new ResourceTemplate("srai://projects/{project_id}/reviews/{review_id}/summary", { list: undefined }), {
47
+ description: "Summary of a specific security review including status, artifact counts, and workflow progress.",
48
+ mimeType: "application/json",
49
+ }, async (uri, variables) => {
50
+ try {
51
+ const projectId = Number(variables.project_id);
52
+ const reviewId = Number(variables.review_id);
53
+ const review = await getApiClient().getReview(projectId, reviewId);
54
+ return {
55
+ contents: [
56
+ {
57
+ uri: uri.href,
58
+ text: jsonStringify(review),
59
+ mimeType: "application/json",
60
+ },
61
+ ],
62
+ };
63
+ }
64
+ catch (error) {
65
+ return resourceError(uri, error);
66
+ }
67
+ });
68
+ server.registerResource("Project Documents", new ResourceTemplate("srai://projects/{project_id}/documents", { list: undefined }), {
69
+ description: "List of all documents in a specific SRAI project.",
70
+ mimeType: "application/json",
71
+ }, async (uri, variables) => {
72
+ try {
73
+ const projectId = Number(variables.project_id);
74
+ const result = await getApiClient().listDocuments(projectId);
75
+ return {
76
+ contents: [
77
+ {
78
+ uri: uri.href,
79
+ text: jsonStringify(result),
80
+ mimeType: "application/json",
81
+ },
82
+ ],
83
+ };
84
+ }
85
+ catch (error) {
86
+ return resourceError(uri, error);
87
+ }
88
+ });
89
+ }
90
+ function resourceError(uri, error) {
91
+ const message = error instanceof SraiApiError ? error.detail : error instanceof Error ? error.message : String(error);
92
+ return {
93
+ contents: [
94
+ {
95
+ uri: uri.href,
96
+ text: jsonStringify({ error: message }),
97
+ mimeType: "application/json",
98
+ },
99
+ ],
100
+ };
101
+ }
package/dist/server.js ADDED
@@ -0,0 +1,38 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { registerAnalysisPrompts } from "./prompts/analysisPrompts.js";
3
+ import { registerGenerationPrompts } from "./prompts/generationPrompts.js";
4
+ import { registerReviewResources } from "./resources/reviewResources.js";
5
+ import { registerDocumentTools } from "./tools/documentTools.js";
6
+ import { registerIntegrationTools } from "./tools/integrationTools.js";
7
+ import { registerProjectTools } from "./tools/projectTools.js";
8
+ import { registerReviewArtifactTools } from "./tools/reviewArtifactTools.js";
9
+ import { registerReviewTools } from "./tools/reviewTools.js";
10
+ import { registerWorkflowTools } from "./tools/workflowTools.js";
11
+ export function createServer() {
12
+ const mcp = new McpServer({
13
+ name: "SRAI Security Review",
14
+ version: "1.0.0",
15
+ description: "You are a security review assistant powered by the SRAI platform. " +
16
+ "When user does not provide numeric IDs, resolve project by name first " +
17
+ "using find_project_by_name, then list and resolve reviews only inside " +
18
+ "that project using list_reviews_by_project_name or list_reviews. " +
19
+ "Never assume project_id=1 or review_id=1. " +
20
+ "You can create projects, upload documents, run security reviews, " +
21
+ "fetch external content (Jira and Confluence), " +
22
+ "and analyze any content for security objectives, components, " +
23
+ "data dictionaries, threat scenarios (product, netwrok, workload type etc), and countermeasures. " +
24
+ "Use the available prompts for structured security analysis guidance." +
25
+ "do not create projects in securityreview until the user explicitly asks to do so" +
26
+ "if he asks to give a threat model in the chat, just give it there.",
27
+ });
28
+ registerProjectTools(mcp);
29
+ registerDocumentTools(mcp);
30
+ registerReviewTools(mcp);
31
+ registerWorkflowTools(mcp);
32
+ registerIntegrationTools(mcp);
33
+ registerReviewArtifactTools(mcp);
34
+ registerAnalysisPrompts(mcp);
35
+ registerGenerationPrompts(mcp);
36
+ registerReviewResources(mcp);
37
+ return mcp;
38
+ }
@@ -0,0 +1,14 @@
1
+ import { SraiApiError } from "../api/client.js";
2
+ import { errorToolResult, textToolResult } from "../utils/serialization.js";
3
+ export async function runTool(task) {
4
+ try {
5
+ const result = await task();
6
+ return textToolResult(result);
7
+ }
8
+ catch (error) {
9
+ if (error instanceof SraiApiError) {
10
+ return errorToolResult(error.detail, error.statusCode);
11
+ }
12
+ return errorToolResult(error instanceof Error ? error.message : String(error));
13
+ }
14
+ }
@@ -0,0 +1,140 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import * as z from "zod/v4";
5
+ import { getApiClient } from "../api/client.js";
6
+ import { errorToolResult, textToolResult } from "../utils/serialization.js";
7
+ import { runTool } from "./common.js";
8
+ export function registerDocumentTools(server) {
9
+ server.registerTool("list_documents", {
10
+ description: "List all documents in an SRAI project. Returns document IDs, names, types, and processing status. Use this to find documents for a review.",
11
+ inputSchema: {
12
+ project_id: z.number().int(),
13
+ },
14
+ }, async ({ project_id }) => runTool(async () => getApiClient().listDocuments(project_id)));
15
+ server.registerTool("get_document", {
16
+ description: "Get detailed information about a specific document in an SRAI project. Returns name, description, type, file path, and processing status.",
17
+ inputSchema: {
18
+ project_id: z.number().int(),
19
+ document_id: z.number().int(),
20
+ },
21
+ }, async ({ project_id, document_id }) => runTool(async () => getApiClient().getDocument(project_id, document_id)));
22
+ server.registerTool("get_document_evaluation", {
23
+ description: "Get the evaluation data for a processed document. Returns the extracted content summary and quality assessment. Only available after document processing is complete.",
24
+ inputSchema: {
25
+ project_id: z.number().int(),
26
+ document_id: z.number().int(),
27
+ },
28
+ }, async ({ project_id, document_id }) => runTool(async () => getApiClient().getDocumentEvaluation(project_id, document_id)));
29
+ server.registerTool("upload_component_diagram", {
30
+ description: "Upload a component diagram image file to an SRAI project using a local file path. This is optimized for IDE usage where the user provides a path from their local machine. Accepted file extensions: .png, .jpg, .jpeg.",
31
+ inputSchema: {
32
+ project_id: z.number().int(),
33
+ name: z.string(),
34
+ description: z.string(),
35
+ file_path: z.string(),
36
+ },
37
+ }, async ({ project_id, name, description, file_path }) => {
38
+ try {
39
+ const resolvedPath = expandUserPath(file_path);
40
+ const stat = await fs.stat(resolvedPath).catch(() => undefined);
41
+ if (!stat || !stat.isFile()) {
42
+ return errorToolResult(`File not found or not a file: ${file_path}`, 400);
43
+ }
44
+ const extension = path.extname(resolvedPath).toLowerCase();
45
+ if (![".png", ".jpg", ".jpeg"].includes(extension)) {
46
+ return errorToolResult("Invalid diagram file type. Accepted extensions are: .png, .jpg, .jpeg", 400);
47
+ }
48
+ const fileBytes = await fs.readFile(resolvedPath);
49
+ const result = await getApiClient().uploadComponentDiagram(project_id, name, description, fileBytes, path.basename(resolvedPath), contentTypeForExtension(extension));
50
+ return textToolResult(result);
51
+ }
52
+ catch (error) {
53
+ return errorToolResult(error instanceof Error ? `Unable to read file: ${error.message}` : String(error), 400);
54
+ }
55
+ });
56
+ server.registerTool("upload_document", {
57
+ description: "Upload a knowledge base document or component diagram to an SRAI project. Provide file content as base64-encoded string, file name, and document type. Valid document_type values: 'Uploaded Knowledgebase doc', 'Component Diagram'. The document will be processed asynchronously after upload.",
58
+ inputSchema: {
59
+ project_id: z.number().int(),
60
+ name: z.string(),
61
+ description: z.string(),
62
+ document_type: z.string(),
63
+ file_content_base64: z.string(),
64
+ file_name: z.string(),
65
+ },
66
+ }, async ({ project_id, name, description, document_type, file_content_base64, file_name }) => runTool(async () => getApiClient().uploadDocument(project_id, name, description, document_type, Buffer.from(file_content_base64, "base64"), file_name)));
67
+ server.registerTool("create_document_from_content", {
68
+ description: "Create and upload a document from raw text/markdown content. The content is converted to a .txt file and uploaded to the SRAI project. Use this when you have generated or received text content that needs to be saved as a document in SRAI for security review. The LLM should generate detailed content before calling this tool.",
69
+ inputSchema: {
70
+ project_id: z.number().int(),
71
+ name: z.string(),
72
+ description: z.string(),
73
+ content: z.string(),
74
+ },
75
+ }, async ({ project_id, name, description, content }) => runTool(async () => getApiClient().uploadDocument(project_id, name, description, "Uploaded Knowledgebase doc", Buffer.from(content, "utf8"), `${name.replaceAll(" ", "_").toLowerCase()}.txt`)));
76
+ server.registerTool("link_external_document", {
77
+ description: "Link an external document to an SRAI project. Supports linking from Jira issues, Confluence pages, and GitHub repositories. Provide the appropriate source-specific fields in the payload.\n\nFor Jira: include document_type='Jira Issue', jira_issue_id, and optionally jira_project_key or jira_issue_url.\nFor Confluence: include document_type='Confluence Page', confluence_page_id, confluence_space_key, or confluence_page_url.\nFor GitHub: include document_type='Github Repository' and github_repo_url.",
78
+ inputSchema: {
79
+ project_id: z.number().int(),
80
+ name: z.string(),
81
+ description: z.string(),
82
+ document_type: z.string(),
83
+ source_id: z.string().default(""),
84
+ source_url: z.string().default(""),
85
+ source_key: z.string().default(""),
86
+ },
87
+ }, async ({ project_id, name, description, document_type, source_id, source_url, source_key }) => runTool(async () => {
88
+ const payload = {
89
+ name,
90
+ description,
91
+ document_type,
92
+ };
93
+ const docTypeLower = document_type.toLowerCase();
94
+ if (docTypeLower.includes("jira")) {
95
+ if (source_url) {
96
+ payload.jira_issue_url = source_url;
97
+ }
98
+ if (source_id) {
99
+ payload.jira_issue_id = source_id;
100
+ }
101
+ if (source_key) {
102
+ payload.jira_project_key = source_key;
103
+ }
104
+ }
105
+ else if (docTypeLower.includes("confluence")) {
106
+ if (source_url) {
107
+ payload.confluence_page_url = source_url;
108
+ }
109
+ if (source_id) {
110
+ payload.confluence_page_id = source_id;
111
+ }
112
+ if (source_key) {
113
+ payload.confluence_space_key = source_key;
114
+ }
115
+ }
116
+ else if (docTypeLower.includes("github")) {
117
+ if (source_url) {
118
+ payload.github_repo_url = source_url;
119
+ }
120
+ }
121
+ return getApiClient().linkDocument(project_id, payload);
122
+ }));
123
+ }
124
+ function expandUserPath(filePath) {
125
+ if (filePath.startsWith("~/")) {
126
+ return path.join(os.homedir(), filePath.slice(2));
127
+ }
128
+ return filePath;
129
+ }
130
+ function contentTypeForExtension(extension) {
131
+ switch (extension) {
132
+ case ".png":
133
+ return "image/png";
134
+ case ".jpg":
135
+ case ".jpeg":
136
+ return "image/jpeg";
137
+ default:
138
+ return "application/octet-stream";
139
+ }
140
+ }
@@ -0,0 +1,100 @@
1
+ import * as z from "zod/v4";
2
+ import { getApiClient, SraiApiError } from "../api/client.js";
3
+ import { ConfluenceClient } from "../integrations/confluenceClient.js";
4
+ import { JiraClient } from "../integrations/jiraClient.js";
5
+ import { errorToolResult, textToolResult } from "../utils/serialization.js";
6
+ export function registerIntegrationTools(server) {
7
+ server.registerTool("fetch_jira_issue", {
8
+ description: "Fetch a Jira issue by its key (e.g., 'PROJ-123') or URL. Returns the issue summary, description, acceptance criteria, comments, status, priority, and labels. The fetched content can then be analyzed using the security analysis prompts, or linked to an SRAI project. Requires JIRA_BASE_URL, JIRA_EMAIL, and JIRA_API_TOKEN environment variables.",
9
+ inputSchema: {
10
+ issue_id: z.string(),
11
+ },
12
+ }, async ({ issue_id }) => textToolResult(await new JiraClient().fetchIssue(issue_id)));
13
+ server.registerTool("fetch_confluence_page", {
14
+ description: "Fetch a Confluence page by its ID or URL. Returns the page title, space, and full text content. The fetched content can then be analyzed using the security analysis prompts, or linked to an SRAI project. Requires CONFLUENCE_BASE_URL, CONFLUENCE_EMAIL, and CONFLUENCE_API_TOKEN environment variables.",
15
+ inputSchema: {
16
+ page_id: z.string().default(""),
17
+ page_url: z.string().default(""),
18
+ },
19
+ }, async ({ page_id, page_url }) => {
20
+ try {
21
+ const client = new ConfluenceClient();
22
+ if (page_url) {
23
+ return textToolResult(await client.fetchPageByUrl(page_url));
24
+ }
25
+ if (page_id) {
26
+ return textToolResult(await client.fetchPage(page_id));
27
+ }
28
+ return errorToolResult("Provide either page_id or page_url");
29
+ }
30
+ catch (error) {
31
+ return errorToolResult(error instanceof Error ? error.message : String(error));
32
+ }
33
+ });
34
+ server.registerTool("search_confluence_pages", {
35
+ description: "Search Confluence pages using CQL and return matching pages with content. Use this when you don't have a page ID/URL yet or need to fetch multiple pages. Examples: cql='space=ENG and type=page and title~\"Threat Model\"'. Requires CONFLUENCE_BASE_URL, CONFLUENCE_EMAIL, and CONFLUENCE_API_TOKEN.",
36
+ inputSchema: {
37
+ cql: z.string(),
38
+ limit: z.number().int().default(10),
39
+ include_content: z.boolean().default(true),
40
+ },
41
+ }, async ({ cql, limit, include_content }) => {
42
+ try {
43
+ const result = await new ConfluenceClient().searchPages(cql, limit, include_content);
44
+ return textToolResult(result);
45
+ }
46
+ catch (error) {
47
+ return errorToolResult(error instanceof Error ? error.message : String(error));
48
+ }
49
+ });
50
+ server.registerTool("fetch_and_link_to_srai", {
51
+ description: "Fetch content from an external source AND link it as a document in an SRAI project. This is a convenience tool that combines fetching (from Jira or Confluence) with linking it to a project.\n\nParameters:\n- project_id: The SRAI project to link to\n- source_type: 'jira' or 'confluence'\n- source_identifier: The issue key or page ID/URL\n- name: Document name in SRAI\n- description: Document description in SRAI",
52
+ inputSchema: {
53
+ project_id: z.number().int(),
54
+ source_type: z.string(),
55
+ source_identifier: z.string(),
56
+ name: z.string(),
57
+ description: z.string(),
58
+ },
59
+ }, async ({ project_id, source_type, source_identifier, name, description }) => {
60
+ const docTypeMap = {
61
+ jira: "Jira Issue",
62
+ confluence: "Confluence Page",
63
+ };
64
+ const docType = docTypeMap[source_type.toLowerCase()];
65
+ if (!docType) {
66
+ return errorToolResult(`Unknown source_type: ${source_type}. Use: jira, confluence`);
67
+ }
68
+ const payload = {
69
+ name,
70
+ description,
71
+ document_type: docType,
72
+ };
73
+ if (source_type.toLowerCase() === "jira") {
74
+ if (source_identifier.includes("://")) {
75
+ payload.jira_issue_url = source_identifier;
76
+ }
77
+ else {
78
+ payload.jira_issue_id = source_identifier;
79
+ }
80
+ }
81
+ else if (source_type.toLowerCase() === "confluence") {
82
+ if (source_identifier.includes("://")) {
83
+ payload.confluence_page_url = source_identifier;
84
+ }
85
+ else {
86
+ payload.confluence_page_id = source_identifier;
87
+ }
88
+ }
89
+ try {
90
+ const result = await getApiClient().linkDocument(project_id, payload);
91
+ return textToolResult(result);
92
+ }
93
+ catch (error) {
94
+ if (error instanceof SraiApiError) {
95
+ return errorToolResult(error.detail, error.statusCode);
96
+ }
97
+ return errorToolResult(error instanceof Error ? error.message : String(error));
98
+ }
99
+ });
100
+ }