This is page 1 of 2. Use http://codebase.md/spathodea-network/opencti-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .env.example ├── .gitignore ├── Dockerfile ├── LICENSE ├── package.json ├── README.md ├── README.zh-TW.md ├── smithery.yaml ├── src │ ├── index.ts │ ├── opencti.graphql │ └── queries │ ├── metadata.ts │ ├── references.ts │ ├── relationships.ts │ ├── reports.ts │ ├── stix_objects.ts │ ├── system.ts │ └── users.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` 1 | # OpenCTI Configuration 2 | OPENCTI_URL=http://localhost:8080 3 | OPENCTI_TOKEN=your-api-token-here 4 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Dependencies 2 | node_modules/ 3 | package-lock.json 4 | 5 | # Build output 6 | build/ 7 | dist/ 8 | 9 | # Environment files 10 | .env 11 | .env.local 12 | .env.*.local 13 | 14 | # IDE files 15 | .vscode/ 16 | .idea/ 17 | *.iml 18 | 19 | # Logs 20 | logs/ 21 | *.log 22 | npm-debug.log* 23 | 24 | # System files 25 | .DS_Store 26 | Thumbs.db 27 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # OpenCTI MCP Server 2 | 3 | [](https://smithery.ai/server/opencti-server) 4 | [Traditional Chinese (繁體中文)](README.zh-TW.md) 5 | 6 | <a href="https://glama.ai/mcp/servers/ml61kiz1gm"><img width="380" height="200" src="https://glama.ai/mcp/servers/ml61kiz1gm/badge" alt="OpenCTI Server MCP server" /></a> 7 | 8 | ## Overview 9 | OpenCTI MCP Server is a Model Context Protocol (MCP) server that provides seamless integration with OpenCTI (Open Cyber Threat Intelligence) platform. It enables querying and retrieving threat intelligence data through a standardized interface. 10 | 11 | ## Features 12 | - Fetch and search threat intelligence data 13 | - Get latest reports and search by ID 14 | - Search for malware information 15 | - Query indicators of compromise 16 | - Search for threat actors 17 | - User and group management 18 | - List all users and groups 19 | - Get user details by ID 20 | - STIX object operations 21 | - List attack patterns 22 | - Get campaign information by name 23 | - System management 24 | - List connectors 25 | - View status templates 26 | - File operations 27 | - List all files 28 | - Get file details by ID 29 | - Reference data access 30 | - List marking definitions 31 | - View available labels 32 | - Customizable query limits 33 | - Full GraphQL query support 34 | 35 | ## Prerequisites 36 | - Node.js 16 or higher 37 | - Access to an OpenCTI instance 38 | - OpenCTI API token 39 | 40 | ## Installation 41 | 42 | ### Installing via Smithery 43 | 44 | To install OpenCTI Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/opencti-server): 45 | 46 | ```bash 47 | npx -y @smithery/cli install opencti-server --client claude 48 | ``` 49 | 50 | ### Manual Installation 51 | ```bash 52 | # Clone the repository 53 | git clone https://github.com/yourusername/opencti-mcp-server.git 54 | 55 | # Install dependencies 56 | cd opencti-mcp-server 57 | npm install 58 | 59 | # Build the project 60 | npm run build 61 | ``` 62 | 63 | ## Configuration 64 | 65 | ### Environment Variables 66 | Copy `.env.example` to `.env` and update with your OpenCTI credentials: 67 | ```bash 68 | cp .env.example .env 69 | ``` 70 | 71 | Required environment variables: 72 | - `OPENCTI_URL`: Your OpenCTI instance URL 73 | - `OPENCTI_TOKEN`: Your OpenCTI API token 74 | 75 | ### MCP Settings 76 | Create a configuration file in your MCP settings location: 77 | ```json 78 | { 79 | "mcpServers": { 80 | "opencti": { 81 | "command": "node", 82 | "args": ["path/to/opencti-server/build/index.js"], 83 | "env": { 84 | "OPENCTI_URL": "${OPENCTI_URL}", // Will be loaded from .env 85 | "OPENCTI_TOKEN": "${OPENCTI_TOKEN}" // Will be loaded from .env 86 | } 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ### Security Notes 93 | - Never commit `.env` file or API tokens to version control 94 | - Keep your OpenCTI credentials secure 95 | - The `.gitignore` file is configured to exclude sensitive files 96 | 97 | ## Available Tools 98 | 99 | ## Available Tools 100 | 101 | ### Reports 102 | #### get_latest_reports 103 | Retrieves the most recent threat intelligence reports. 104 | ```typescript 105 | { 106 | "name": "get_latest_reports", 107 | "arguments": { 108 | "first": 10 // Optional, defaults to 10 109 | } 110 | } 111 | ``` 112 | 113 | #### get_report_by_id 114 | Retrieves a specific report by its ID. 115 | ```typescript 116 | { 117 | "name": "get_report_by_id", 118 | "arguments": { 119 | "id": "report-uuid" // Required 120 | } 121 | } 122 | ``` 123 | 124 | ### Search Operations 125 | #### search_malware 126 | Searches for malware information in the OpenCTI database. 127 | ```typescript 128 | { 129 | "name": "search_malware", 130 | "arguments": { 131 | "query": "ransomware", 132 | "first": 10 // Optional, defaults to 10 133 | } 134 | } 135 | ``` 136 | 137 | #### search_indicators 138 | Searches for indicators of compromise. 139 | ```typescript 140 | { 141 | "name": "search_indicators", 142 | "arguments": { 143 | "query": "domain", 144 | "first": 10 // Optional, defaults to 10 145 | } 146 | } 147 | ``` 148 | 149 | #### search_threat_actors 150 | Searches for threat actor information. 151 | ```typescript 152 | { 153 | "name": "search_threat_actors", 154 | "arguments": { 155 | "query": "APT", 156 | "first": 10 // Optional, defaults to 10 157 | } 158 | } 159 | ``` 160 | 161 | ### User Management 162 | #### get_user_by_id 163 | Retrieves user information by ID. 164 | ```typescript 165 | { 166 | "name": "get_user_by_id", 167 | "arguments": { 168 | "id": "user-uuid" // Required 169 | } 170 | } 171 | ``` 172 | 173 | #### list_users 174 | Lists all users in the system. 175 | ```typescript 176 | { 177 | "name": "list_users", 178 | "arguments": {} 179 | } 180 | ``` 181 | 182 | #### list_groups 183 | Lists all groups with their members. 184 | ```typescript 185 | { 186 | "name": "list_groups", 187 | "arguments": { 188 | "first": 10 // Optional, defaults to 10 189 | } 190 | } 191 | ``` 192 | 193 | ### STIX Objects 194 | #### list_attack_patterns 195 | Lists all attack patterns in the system. 196 | ```typescript 197 | { 198 | "name": "list_attack_patterns", 199 | "arguments": { 200 | "first": 10 // Optional, defaults to 10 201 | } 202 | } 203 | ``` 204 | 205 | #### get_campaign_by_name 206 | Retrieves campaign information by name. 207 | ```typescript 208 | { 209 | "name": "get_campaign_by_name", 210 | "arguments": { 211 | "name": "campaign-name" // Required 212 | } 213 | } 214 | ``` 215 | 216 | ### System Management 217 | #### list_connectors 218 | Lists all system connectors. 219 | ```typescript 220 | { 221 | "name": "list_connectors", 222 | "arguments": {} 223 | } 224 | ``` 225 | 226 | #### list_status_templates 227 | Lists all status templates. 228 | ```typescript 229 | { 230 | "name": "list_status_templates", 231 | "arguments": {} 232 | } 233 | ``` 234 | 235 | ### File Operations 236 | #### get_file_by_id 237 | Retrieves file information by ID. 238 | ```typescript 239 | { 240 | "name": "get_file_by_id", 241 | "arguments": { 242 | "id": "file-uuid" // Required 243 | } 244 | } 245 | ``` 246 | 247 | #### list_files 248 | Lists all files in the system. 249 | ```typescript 250 | { 251 | "name": "list_files", 252 | "arguments": {} 253 | } 254 | ``` 255 | 256 | ### Reference Data 257 | #### list_marking_definitions 258 | Lists all marking definitions. 259 | ```typescript 260 | { 261 | "name": "list_marking_definitions", 262 | "arguments": {} 263 | } 264 | ``` 265 | 266 | #### list_labels 267 | Lists all available labels. 268 | ```typescript 269 | { 270 | "name": "list_labels", 271 | "arguments": {} 272 | } 273 | ``` 274 | 275 | ## Contributing 276 | Contributions are welcome! Please feel free to submit pull requests. 277 | 278 | ## License 279 | MIT License 280 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "opencti-server", 3 | "version": "0.1.0", 4 | "description": "A Model Context Protocol server", 5 | "private": true, 6 | "type": "module", 7 | "bin": { 8 | "opencti-server": "./build/index.js" 9 | }, 10 | "files": [ 11 | "build" 12 | ], 13 | "scripts": { 14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 15 | "prepare": "npm run build", 16 | "watch": "tsc --watch", 17 | "inspector": "npx @modelcontextprotocol/inspector build/index.js" 18 | }, 19 | "dependencies": { 20 | "@modelcontextprotocol/sdk": "0.6.0", 21 | "axios": "^1.7.9" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^20.11.24", 25 | "typescript": "^5.3.3" 26 | } 27 | } 28 | ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - openctiUrl 10 | - openctiToken 11 | properties: 12 | openctiUrl: 13 | type: string 14 | description: The URL of the OpenCTI instance. 15 | openctiToken: 16 | type: string 17 | description: The API token for the OpenCTI instance. 18 | commandFunction: 19 | # A function that produces the CLI command to start the MCP on stdio. 20 | |- 21 | (config) => ({command: 'node', args: ['build/index.js'], env: {OPENCTI_URL: config.openctiUrl, OPENCTI_TOKEN: config.openctiToken}}) 22 | ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | # Use a Node.js image with the appropriate version 3 | FROM node:16-alpine AS builder 4 | 5 | # Set working directory 6 | WORKDIR /app 7 | 8 | # Copy package.json and package-lock.json for dependency installation 9 | COPY package.json package.json 10 | 11 | # Install dependencies 12 | RUN npm install --ignore-scripts 13 | 14 | # Copy the rest of the application source code 15 | COPY . . 16 | 17 | # Build the TypeScript project 18 | RUN npm run build 19 | 20 | # Prepare the runtime environment 21 | FROM node:16-alpine AS release 22 | 23 | # Set working directory 24 | WORKDIR /app 25 | 26 | # Copy built files and necessary configuration 27 | COPY --from=builder /app/build /app/build 28 | COPY --from=builder /app/package.json /app/package.json 29 | 30 | # Set environment variables 31 | ENV OPENCTI_URL=https://your-opencti-url 32 | ENV OPENCTI_TOKEN=your-opencti-token 33 | 34 | # Run the server 35 | ENTRYPOINT ["node", "build/index.js"] 36 | ``` -------------------------------------------------------------------------------- /src/queries/references.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const ALL_MARKING_DEFINITIONS_QUERY = ` 2 | query AllMarkingDefinitions { 3 | markingDefinitions { 4 | edges { 5 | node { 6 | id 7 | standard_id 8 | entity_type 9 | definition_type 10 | definition 11 | x_opencti_order 12 | x_opencti_color 13 | } 14 | } 15 | } 16 | } 17 | `; 18 | 19 | export const ALL_LABELS_QUERY = ` 20 | query AllLabels { 21 | labels { 22 | edges { 23 | node { 24 | id 25 | standard_id 26 | entity_type 27 | value 28 | color 29 | } 30 | } 31 | } 32 | } 33 | `; 34 | 35 | export const ALL_EXTERNAL_REFERENCES_QUERY = ` 36 | query AllExternalReferences { 37 | externalReferences { 38 | edges { 39 | node { 40 | id 41 | standard_id 42 | entity_type 43 | source_name 44 | description 45 | url 46 | hash 47 | external_id 48 | } 49 | } 50 | } 51 | } 52 | `; 53 | 54 | export const ALL_KILL_CHAIN_PHASES_QUERY = ` 55 | query AllKillChainPhases { 56 | killChainPhases { 57 | edges { 58 | node { 59 | id 60 | standard_id 61 | entity_type 62 | kill_chain_name 63 | phase_name 64 | x_opencti_order 65 | } 66 | } 67 | } 68 | } 69 | `; 70 | ``` -------------------------------------------------------------------------------- /src/queries/users.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const USER_BY_ID_QUERY = ` 2 | query UserById($id: String!) { 3 | user(id: $id) { 4 | id 5 | standard_id 6 | entity_type 7 | parent_types 8 | user_email 9 | name 10 | firstname 11 | lastname 12 | groups { 13 | edges { 14 | node { 15 | id 16 | name 17 | } 18 | } 19 | } 20 | } 21 | } 22 | `; 23 | 24 | export const ALL_USERS_QUERY = ` 25 | query AllUsers { 26 | users { 27 | edges { 28 | node { 29 | id 30 | standard_id 31 | entity_type 32 | user_email 33 | name 34 | firstname 35 | lastname 36 | external 37 | created_at 38 | updated_at 39 | } 40 | } 41 | } 42 | } 43 | `; 44 | 45 | export const ALL_GROUPS_QUERY = ` 46 | query AllGroups($first: Int, $after: ID) { 47 | groups(first: $first, after: $after) { 48 | pageInfo { 49 | hasNextPage 50 | endCursor 51 | } 52 | edges { 53 | node { 54 | id 55 | standard_id 56 | entity_type 57 | parent_types 58 | name 59 | description 60 | members(first: 5) { 61 | edges { 62 | node { 63 | id 64 | name 65 | user_email 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | `; 74 | 75 | export const ALL_ROLES_QUERY = ` 76 | query AllRoles { 77 | roles { 78 | edges { 79 | node { 80 | id 81 | standard_id 82 | entity_type 83 | name 84 | description 85 | created_at 86 | updated_at 87 | } 88 | } 89 | } 90 | } 91 | `; 92 | 93 | export const ALL_CAPABILITIES_QUERY = ` 94 | query AllCapabilities { 95 | capabilities { 96 | edges { 97 | node { 98 | id 99 | standard_id 100 | entity_type 101 | name 102 | description 103 | attribute_order 104 | created_at 105 | updated_at 106 | } 107 | } 108 | } 109 | } 110 | `; 111 | ``` -------------------------------------------------------------------------------- /src/queries/metadata.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const FILE_BY_ID_QUERY = ` 2 | query FileById($id: String!) { 3 | file(id: $id) { 4 | id 5 | name 6 | size 7 | lastModified 8 | uploadStatus 9 | } 10 | } 11 | `; 12 | 13 | export const ALL_FILES_QUERY = ` 14 | query AllFiles { 15 | importFiles(first: 100) { 16 | edges { 17 | node { 18 | id 19 | name 20 | size 21 | uploadStatus 22 | lastModified 23 | metaData { 24 | mimetype 25 | version 26 | } 27 | } 28 | } 29 | } 30 | } 31 | `; 32 | 33 | export const ALL_INDEXED_FILES_QUERY = ` 34 | query AllIndexedFiles { 35 | indexedFiles { 36 | edges { 37 | node { 38 | id 39 | name 40 | file_id 41 | uploaded_at 42 | entity { 43 | id 44 | entity_type 45 | parent_types 46 | standard_id 47 | } 48 | searchOccurrences 49 | } 50 | } 51 | } 52 | } 53 | `; 54 | 55 | export const ALL_LOGS_QUERY = ` 56 | query AllLogs { 57 | logs { 58 | edges { 59 | node { 60 | id 61 | entity_type 62 | event_type 63 | event_scope 64 | event_status 65 | timestamp 66 | user_id 67 | user { 68 | id 69 | name 70 | entity_type 71 | } 72 | context_uri 73 | context_data { 74 | entity_id 75 | entity_name 76 | entity_type 77 | from_id 78 | to_id 79 | message 80 | commit 81 | external_references { 82 | id 83 | source_name 84 | description 85 | url 86 | hash 87 | external_id 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | `; 95 | 96 | export const ALL_AUDITS_QUERY = ` 97 | query AllAudits { 98 | audits { 99 | edges { 100 | node { 101 | id 102 | entity_type 103 | event_type 104 | event_scope 105 | event_status 106 | timestamp 107 | user_id 108 | user { 109 | id 110 | name 111 | entity_type 112 | } 113 | context_uri 114 | context_data { 115 | entity_id 116 | entity_name 117 | entity_type 118 | from_id 119 | to_id 120 | message 121 | commit 122 | external_references { 123 | id 124 | source_name 125 | description 126 | url 127 | hash 128 | external_id 129 | } 130 | } 131 | } 132 | } 133 | } 134 | } 135 | `; 136 | 137 | export const ALL_ATTRIBUTES_QUERY = ` 138 | query AllAttributes { 139 | runtimeAttributes { 140 | edges { 141 | node { 142 | id 143 | key 144 | value 145 | } 146 | } 147 | } 148 | } 149 | `; 150 | 151 | export const ALL_SCHEMA_ATTRIBUTE_NAMES_QUERY = ` 152 | query AllSchemaAttributeNames { 153 | schemaAttributeNames(elementType: ["Report", "Note"]) { 154 | edges { 155 | node { 156 | id 157 | key 158 | value 159 | } 160 | } 161 | } 162 | } 163 | `; 164 | 165 | export const ALL_FILTER_KEYS_SCHEMA_QUERY = ` 166 | query AllFilterKeysSchema { 167 | filterKeysSchema { 168 | entity_type 169 | filters_schema { 170 | filterKey 171 | filterDefinition { 172 | filterKey 173 | label 174 | type 175 | multiple 176 | subEntityTypes 177 | elementsForFilterValuesSearch 178 | subFilters { 179 | filterKey 180 | label 181 | type 182 | multiple 183 | subEntityTypes 184 | elementsForFilterValuesSearch 185 | } 186 | } 187 | } 188 | } 189 | } 190 | `; 191 | ``` -------------------------------------------------------------------------------- /src/queries/relationships.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const ALL_STIX_CORE_RELATIONSHIPS_QUERY = ` 2 | query AllStixCoreRelationships($first: Int, $after: ID) { 3 | stixCoreRelationships(first: $first, after: $after) { 4 | pageInfo { 5 | hasNextPage 6 | endCursor 7 | } 8 | edges { 9 | node { 10 | id 11 | standard_id 12 | entity_type 13 | parent_types 14 | relationship_type 15 | confidence 16 | start_time 17 | stop_time 18 | from { 19 | ... on StixDomainObject { 20 | id 21 | entity_type 22 | name 23 | } 24 | ... on StixCyberObservable { 25 | id 26 | entity_type 27 | observable_value 28 | } 29 | } 30 | to { 31 | ... on StixDomainObject { 32 | id 33 | entity_type 34 | name 35 | } 36 | ... on StixCyberObservable { 37 | id 38 | entity_type 39 | observable_value 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | `; 47 | 48 | export const ALL_STIX_SIGHTING_RELATIONSHIPS_QUERY = ` 49 | query AllStixSightingRelationships($first: Int, $after: ID) { 50 | stixSightingRelationships(first: $first, after: $after) { 51 | pageInfo { 52 | hasNextPage 53 | endCursor 54 | } 55 | edges { 56 | node { 57 | id 58 | standard_id 59 | entity_type 60 | parent_types 61 | relationship_type 62 | confidence 63 | first_seen 64 | last_seen 65 | from { 66 | ... on StixDomainObject { 67 | id 68 | entity_type 69 | name 70 | } 71 | ... on StixCyberObservable { 72 | id 73 | entity_type 74 | observable_value 75 | } 76 | } 77 | to { 78 | ... on StixDomainObject { 79 | id 80 | entity_type 81 | name 82 | } 83 | ... on StixCyberObservable { 84 | id 85 | entity_type 86 | observable_value 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | `; 94 | 95 | export const ALL_STIX_REF_RELATIONSHIPS_QUERY = ` 96 | query AllStixRefRelationships($first: Int, $after: ID) { 97 | stixRefRelationships(first: $first, after: $after) { 98 | pageInfo { 99 | hasNextPage 100 | endCursor 101 | } 102 | edges { 103 | node { 104 | id 105 | standard_id 106 | entity_type 107 | parent_types 108 | relationship_type 109 | confidence 110 | from { 111 | ... on StixDomainObject { 112 | id 113 | entity_type 114 | name 115 | } 116 | ... on StixCyberObservable { 117 | id 118 | entity_type 119 | observable_value 120 | } 121 | } 122 | to { 123 | ... on StixDomainObject { 124 | id 125 | entity_type 126 | name 127 | } 128 | ... on StixCyberObservable { 129 | id 130 | entity_type 131 | observable_value 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | `; 139 | 140 | export const ALL_STIX_RELATIONSHIPS_QUERY = ` 141 | query AllStixRelationships { 142 | stixRelationships { 143 | edges { 144 | node { 145 | id 146 | standard_id 147 | entity_type 148 | parent_types 149 | relationship_type 150 | confidence 151 | created_at 152 | updated_at 153 | from { 154 | ... on StixDomainObject { 155 | id 156 | entity_type 157 | name 158 | } 159 | ... on StixCyberObservable { 160 | id 161 | entity_type 162 | observable_value 163 | } 164 | } 165 | to { 166 | ... on StixDomainObject { 167 | id 168 | entity_type 169 | name 170 | } 171 | ... on StixCyberObservable { 172 | id 173 | entity_type 174 | observable_value 175 | } 176 | } 177 | objectMarking { 178 | id 179 | definition 180 | x_opencti_order 181 | x_opencti_color 182 | } 183 | createdBy { 184 | id 185 | name 186 | entity_type 187 | } 188 | } 189 | } 190 | } 191 | } 192 | `; 193 | ``` -------------------------------------------------------------------------------- /src/queries/reports.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const LATEST_REPORTS_QUERY = ` 2 | query LatestReport($first: Int) { 3 | reports( 4 | first: $first, 5 | orderBy: created, 6 | orderMode: desc 7 | ) { 8 | edges { 9 | node { 10 | # Basic fields 11 | id 12 | standard_id 13 | entity_type 14 | parent_types 15 | 16 | # Report specific fields 17 | name 18 | description 19 | content 20 | content_mapping 21 | report_types 22 | published 23 | confidence 24 | createdBy { 25 | id 26 | name 27 | entity_type 28 | } 29 | objectMarking { 30 | id 31 | definition 32 | x_opencti_order 33 | x_opencti_color 34 | } 35 | objectLabel { 36 | id 37 | value 38 | color 39 | } 40 | externalReferences { 41 | edges { 42 | node { 43 | id 44 | source_name 45 | description 46 | url 47 | hash 48 | external_id 49 | } 50 | } 51 | } 52 | 53 | # Relationships and objects 54 | objects(first: 500) { 55 | edges { 56 | node { 57 | ... on StixDomainObject { 58 | id 59 | entity_type 60 | parent_types 61 | created 62 | updated_at 63 | standard_id 64 | created 65 | revoked 66 | confidence 67 | lang 68 | status { 69 | id 70 | template { 71 | name 72 | color 73 | } 74 | } 75 | } 76 | ... on StixCyberObservable { 77 | id 78 | entity_type 79 | parent_types 80 | observable_value 81 | x_opencti_description 82 | x_opencti_score 83 | } 84 | ... on StixCoreRelationship { 85 | id 86 | entity_type 87 | parent_types 88 | relationship_type 89 | description 90 | start_time 91 | stop_time 92 | from { 93 | ... on StixDomainObject { 94 | id 95 | entity_type 96 | parent_types 97 | created_at 98 | standard_id 99 | } 100 | } 101 | to { 102 | ... on StixDomainObject { 103 | id 104 | entity_type 105 | parent_types 106 | created_at 107 | standard_id 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | # Additional metadata 116 | created 117 | modified 118 | created_at 119 | updated_at 120 | x_opencti_stix_ids 121 | status { 122 | id 123 | template { 124 | name 125 | color 126 | } 127 | } 128 | workflowEnabled 129 | 130 | # Container specific fields 131 | containersNumber { 132 | total 133 | count 134 | } 135 | containers { 136 | edges { 137 | node { 138 | id 139 | entity_type 140 | parent_types 141 | created_at 142 | standard_id 143 | } 144 | } 145 | } 146 | } 147 | } 148 | } 149 | } 150 | `; 151 | 152 | export const SEARCH_MALWARE_QUERY = ` 153 | query Malware($search: String, $first: Int) { 154 | stixCoreObjects( 155 | search: $search, 156 | first: $first, 157 | types: ["Malware"] 158 | ) { 159 | edges { 160 | node { 161 | ... on Malware { 162 | id 163 | name 164 | description 165 | created 166 | modified 167 | malware_types 168 | is_family 169 | first_seen 170 | last_seen 171 | } 172 | } 173 | } 174 | } 175 | } 176 | `; 177 | 178 | export const SEARCH_INDICATORS_QUERY = ` 179 | query Indicators($search: String, $first: Int) { 180 | stixCoreObjects( 181 | search: $search, 182 | first: $first, 183 | types: ["Indicator"] 184 | ) { 185 | edges { 186 | node { 187 | ... on Indicator { 188 | id 189 | name 190 | description 191 | created_at 192 | pattern 193 | valid_from 194 | valid_until 195 | x_opencti_score 196 | } 197 | } 198 | } 199 | } 200 | } 201 | `; 202 | 203 | export const SEARCH_THREAT_ACTORS_QUERY = ` 204 | query ThreatActors($search: String, $first: Int) { 205 | stixCoreObjects( 206 | search: $search, 207 | first: $first, 208 | types: ["ThreatActorGroup"] 209 | ) { 210 | edges { 211 | node { 212 | ... on ThreatActorGroup { 213 | id 214 | name 215 | description 216 | created 217 | modified 218 | threat_actor_types 219 | first_seen 220 | last_seen 221 | sophistication 222 | resource_level 223 | roles 224 | goals 225 | } 226 | } 227 | } 228 | } 229 | } 230 | `; 231 | ``` -------------------------------------------------------------------------------- /src/queries/system.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const ALL_CONNECTORS_QUERY = ` 2 | query AllConnectors { 3 | connectors { 4 | id 5 | name 6 | active 7 | auto 8 | only_contextual 9 | playbook_compatible 10 | connector_type 11 | connector_scope 12 | connector_state 13 | connector_schema 14 | connector_schema_ui 15 | connector_state_reset 16 | connector_user_id 17 | updated_at 18 | created_at 19 | config { 20 | connection { 21 | host 22 | vhost 23 | use_ssl 24 | port 25 | user 26 | pass 27 | } 28 | listen 29 | listen_routing 30 | listen_exchange 31 | push 32 | push_routing 33 | push_exchange 34 | } 35 | works { 36 | id 37 | name 38 | status 39 | } 40 | } 41 | } 42 | `; 43 | 44 | export const ALL_STATUS_TEMPLATES_QUERY = ` 45 | query AllStatusTemplates { 46 | statusTemplates { 47 | edges { 48 | node { 49 | id 50 | name 51 | color 52 | editContext { 53 | name 54 | focusOn 55 | } 56 | usages 57 | } 58 | } 59 | } 60 | } 61 | `; 62 | 63 | export const ALL_STATUSES_QUERY = ` 64 | query AllStatuses { 65 | statuses { 66 | edges { 67 | node { 68 | id 69 | template_id 70 | template { 71 | id 72 | name 73 | color 74 | } 75 | type 76 | order 77 | disabled 78 | } 79 | } 80 | } 81 | } 82 | `; 83 | 84 | export const ALL_SUB_TYPES_QUERY = ` 85 | query AllSubTypes { 86 | subTypes { 87 | edges { 88 | node { 89 | id 90 | label 91 | statuses { 92 | id 93 | template { 94 | id 95 | name 96 | color 97 | } 98 | type 99 | order 100 | disabled 101 | } 102 | workflowEnabled 103 | settings { 104 | id 105 | entity_type 106 | parent_types 107 | standard_id 108 | } 109 | } 110 | } 111 | } 112 | } 113 | `; 114 | 115 | export const ALL_RETENTION_RULES_QUERY = ` 116 | query AllRetentionRules { 117 | retentionRules { 118 | edges { 119 | node { 120 | id 121 | standard_id 122 | name 123 | filters 124 | max_retention 125 | retention_unit 126 | last_execution_date 127 | last_deleted_count 128 | remaining_count 129 | scope 130 | } 131 | } 132 | } 133 | } 134 | `; 135 | 136 | export const ALL_BACKGROUND_TASKS_QUERY = ` 137 | query AllBackgroundTasks { 138 | backgroundTasks { 139 | edges { 140 | node { 141 | id 142 | type 143 | initiator { 144 | id 145 | name 146 | entity_type 147 | } 148 | actions { 149 | type 150 | context { 151 | field 152 | type 153 | values 154 | } 155 | } 156 | created_at 157 | last_execution_date 158 | completed 159 | task_expected_number 160 | task_processed_number 161 | errors { 162 | id 163 | timestamp 164 | message 165 | } 166 | } 167 | } 168 | } 169 | } 170 | `; 171 | 172 | export const ALL_FEEDS_QUERY = ` 173 | query AllFeeds { 174 | feeds { 175 | edges { 176 | node { 177 | id 178 | standard_id 179 | name 180 | description 181 | filters 182 | separator 183 | rolling_time 184 | feed_date_attribute 185 | include_header 186 | feed_types 187 | feed_attributes { 188 | attribute 189 | mappings { 190 | type 191 | attribute 192 | } 193 | } 194 | feed_public 195 | authorized_members { 196 | id 197 | name 198 | entity_type 199 | access_right 200 | } 201 | } 202 | } 203 | } 204 | } 205 | `; 206 | 207 | export const ALL_TAXII_COLLECTIONS_QUERY = ` 208 | query AllTaxiiCollections { 209 | taxiiCollections { 210 | edges { 211 | node { 212 | id 213 | name 214 | description 215 | filters 216 | include_inferences 217 | score_to_confidence 218 | taxii_public 219 | authorized_members { 220 | id 221 | name 222 | entity_type 223 | access_right 224 | } 225 | } 226 | } 227 | } 228 | } 229 | `; 230 | 231 | export const ALL_STREAM_COLLECTIONS_QUERY = ` 232 | query AllStreamCollections { 233 | streamCollections { 234 | edges { 235 | node { 236 | id 237 | name 238 | description 239 | filters 240 | stream_live 241 | stream_public 242 | authorized_members { 243 | id 244 | name 245 | entity_type 246 | access_right 247 | } 248 | } 249 | } 250 | } 251 | } 252 | `; 253 | 254 | export const ALL_RULES_QUERY = ` 255 | query AllRules { 256 | rules { 257 | id 258 | name 259 | description 260 | activated 261 | category 262 | display { 263 | if { 264 | source 265 | source_color 266 | relation 267 | target 268 | target_color 269 | identifier 270 | identifier_color 271 | action 272 | } 273 | then { 274 | source 275 | source_color 276 | relation 277 | target 278 | target_color 279 | identifier 280 | identifier_color 281 | action 282 | } 283 | } 284 | } 285 | } 286 | `; 287 | 288 | export const ALL_SYNCHRONIZERS_QUERY = ` 289 | query AllSynchronizers { 290 | synchronizers { 291 | edges { 292 | node { 293 | id 294 | name 295 | uri 296 | token 297 | stream_id 298 | user { 299 | id 300 | name 301 | entity_type 302 | } 303 | running 304 | current_state_date 305 | listen_deletion 306 | no_dependencies 307 | ssl_verify 308 | synchronized 309 | queue_messages 310 | } 311 | } 312 | } 313 | } 314 | `; 315 | ``` -------------------------------------------------------------------------------- /src/queries/stix_objects.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const REPORT_BY_ID_QUERY = ` 2 | query ReportById($id: String!) { 3 | report(id: $id) { 4 | id 5 | standard_id 6 | entity_type 7 | parent_types 8 | name 9 | description 10 | content 11 | content_mapping 12 | report_types 13 | published 14 | confidence 15 | createdBy { 16 | id 17 | name 18 | entity_type 19 | } 20 | objectMarking { 21 | id 22 | definition 23 | x_opencti_order 24 | x_opencti_color 25 | } 26 | objectLabel { 27 | id 28 | value 29 | color 30 | } 31 | externalReferences { 32 | edges { 33 | node { 34 | id 35 | source_name 36 | description 37 | url 38 | hash 39 | external_id 40 | } 41 | } 42 | } 43 | objects(first: 500) { 44 | edges { 45 | node { 46 | ... on StixDomainObject { 47 | id 48 | entity_type 49 | parent_types 50 | created 51 | updated_at 52 | standard_id 53 | created 54 | revoked 55 | confidence 56 | lang 57 | status { 58 | id 59 | template { 60 | name 61 | color 62 | } 63 | } 64 | } 65 | ... on StixCyberObservable { 66 | id 67 | entity_type 68 | parent_types 69 | observable_value 70 | x_opencti_description 71 | x_opencti_score 72 | } 73 | ... on StixCoreRelationship { 74 | id 75 | entity_type 76 | parent_types 77 | relationship_type 78 | description 79 | start_time 80 | stop_time 81 | from { 82 | ... on StixDomainObject { 83 | id 84 | entity_type 85 | parent_types 86 | created_at 87 | standard_id 88 | } 89 | } 90 | to { 91 | ... on StixDomainObject { 92 | id 93 | entity_type 94 | parent_types 95 | created_at 96 | standard_id 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | created 104 | modified 105 | created_at 106 | updated_at 107 | x_opencti_stix_ids 108 | status { 109 | id 110 | template { 111 | name 112 | color 113 | } 114 | } 115 | workflowEnabled 116 | containersNumber { 117 | total 118 | count 119 | } 120 | containers { 121 | edges { 122 | node { 123 | id 124 | entity_type 125 | parent_types 126 | created_at 127 | standard_id 128 | } 129 | } 130 | } 131 | } 132 | } 133 | `; 134 | 135 | export const ALL_ATTACK_PATTERNS_QUERY = ` 136 | query AllAttackPatterns($first: Int, $after: ID) { 137 | attackPatterns(first: $first, after: $after) { 138 | pageInfo { 139 | hasNextPage 140 | endCursor 141 | } 142 | edges { 143 | node { 144 | id 145 | standard_id 146 | entity_type 147 | parent_types 148 | name 149 | description 150 | x_mitre_id 151 | killChainPhases { 152 | id 153 | kill_chain_name 154 | phase_name 155 | } 156 | coursesOfAction { 157 | edges { 158 | node { 159 | id 160 | name 161 | description 162 | } 163 | } 164 | } 165 | } 166 | } 167 | } 168 | } 169 | `; 170 | 171 | export const CAMPAIGN_BY_NAME_QUERY = ` 172 | query CampaignByName($name: Any!) { 173 | campaigns( 174 | first: 1, 175 | filters: { 176 | mode: and, 177 | filters: [ 178 | { 179 | key: "name", 180 | values: [$name], 181 | operator: eq, 182 | mode: or 183 | } 184 | ], 185 | filterGroups: [] 186 | } 187 | ) { 188 | edges { 189 | node { 190 | id 191 | standard_id 192 | entity_type 193 | parent_types 194 | name 195 | description 196 | first_seen 197 | last_seen 198 | created 199 | modified 200 | created_at 201 | updated_at 202 | } 203 | } 204 | } 205 | } 206 | `; 207 | 208 | export const ALL_STIX_CORE_OBJECTS_QUERY = ` 209 | query AllStixCoreObjects { 210 | stixCoreObjects { 211 | edges { 212 | node { 213 | id 214 | standard_id 215 | entity_type 216 | parent_types 217 | representative { 218 | main 219 | secondary 220 | } 221 | x_opencti_stix_ids 222 | is_inferred 223 | spec_version 224 | created_at 225 | updated_at 226 | createdBy { 227 | id 228 | name 229 | entity_type 230 | } 231 | numberOfConnectedElement 232 | objectMarking { 233 | id 234 | definition 235 | x_opencti_order 236 | x_opencti_color 237 | } 238 | objectOrganization { 239 | id 240 | name 241 | } 242 | objectLabel { 243 | id 244 | value 245 | color 246 | } 247 | externalReferences { 248 | edges { 249 | node { 250 | id 251 | source_name 252 | description 253 | url 254 | hash 255 | external_id 256 | } 257 | } 258 | } 259 | containersNumber { 260 | total 261 | count 262 | } 263 | containers { 264 | edges { 265 | node { 266 | id 267 | entity_type 268 | parent_types 269 | created_at 270 | standard_id 271 | } 272 | } 273 | } 274 | reports { 275 | edges { 276 | node { 277 | id 278 | name 279 | } 280 | } 281 | } 282 | notes { 283 | edges { 284 | node { 285 | id 286 | content 287 | } 288 | } 289 | } 290 | opinions { 291 | edges { 292 | node { 293 | id 294 | opinion 295 | } 296 | } 297 | } 298 | observedData { 299 | edges { 300 | node { 301 | id 302 | first_observed 303 | last_observed 304 | number_observed 305 | } 306 | } 307 | } 308 | groupings { 309 | edges { 310 | node { 311 | id 312 | name 313 | } 314 | } 315 | } 316 | cases { 317 | edges { 318 | node { 319 | id 320 | name 321 | } 322 | } 323 | } 324 | } 325 | } 326 | } 327 | } 328 | `; 329 | 330 | export const ALL_STIX_DOMAIN_OBJECTS_QUERY = ` 331 | query AllStixDomainObjects { 332 | stixDomainObjects { 333 | edges { 334 | node { 335 | id 336 | standard_id 337 | entity_type 338 | parent_types 339 | representative { 340 | main 341 | secondary 342 | } 343 | x_opencti_stix_ids 344 | is_inferred 345 | spec_version 346 | created_at 347 | updated_at 348 | createdBy { 349 | id 350 | name 351 | entity_type 352 | } 353 | numberOfConnectedElement 354 | objectMarking { 355 | id 356 | definition 357 | x_opencti_order 358 | x_opencti_color 359 | } 360 | objectOrganization { 361 | id 362 | name 363 | } 364 | objectLabel { 365 | id 366 | value 367 | color 368 | } 369 | externalReferences { 370 | edges { 371 | node { 372 | id 373 | source_name 374 | description 375 | url 376 | hash 377 | external_id 378 | } 379 | } 380 | } 381 | containersNumber { 382 | total 383 | count 384 | } 385 | containers { 386 | edges { 387 | node { 388 | id 389 | entity_type 390 | parent_types 391 | created_at 392 | standard_id 393 | } 394 | } 395 | } 396 | reports { 397 | edges { 398 | node { 399 | id 400 | name 401 | } 402 | } 403 | } 404 | notes { 405 | edges { 406 | node { 407 | id 408 | content 409 | } 410 | } 411 | } 412 | opinions { 413 | edges { 414 | node { 415 | id 416 | opinion 417 | } 418 | } 419 | } 420 | observedData { 421 | edges { 422 | node { 423 | id 424 | first_observed 425 | last_observed 426 | number_observed 427 | } 428 | } 429 | } 430 | groupings { 431 | edges { 432 | node { 433 | id 434 | name 435 | } 436 | } 437 | } 438 | cases { 439 | edges { 440 | node { 441 | id 442 | name 443 | } 444 | } 445 | } 446 | revoked 447 | confidence 448 | lang 449 | created 450 | modified 451 | x_opencti_graph_data 452 | objectAssignee { 453 | id 454 | name 455 | entity_type 456 | } 457 | objectParticipant { 458 | id 459 | name 460 | entity_type 461 | } 462 | status { 463 | id 464 | template { 465 | name 466 | color 467 | } 468 | } 469 | workflowEnabled 470 | } 471 | } 472 | } 473 | } 474 | `; 475 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { 5 | CallToolRequestSchema, 6 | ErrorCode, 7 | ListToolsRequestSchema, 8 | McpError, 9 | } from '@modelcontextprotocol/sdk/types.js'; 10 | import axios from 'axios'; 11 | import { 12 | LATEST_REPORTS_QUERY, 13 | SEARCH_MALWARE_QUERY, 14 | SEARCH_INDICATORS_QUERY, 15 | SEARCH_THREAT_ACTORS_QUERY, 16 | } from './queries/reports.js'; 17 | import { 18 | USER_BY_ID_QUERY, 19 | ALL_USERS_QUERY, 20 | ALL_GROUPS_QUERY, 21 | ALL_ROLES_QUERY, 22 | ALL_CAPABILITIES_QUERY, 23 | } from './queries/users.js'; 24 | import { 25 | REPORT_BY_ID_QUERY, 26 | ALL_ATTACK_PATTERNS_QUERY, 27 | CAMPAIGN_BY_NAME_QUERY, 28 | ALL_STIX_CORE_OBJECTS_QUERY, 29 | ALL_STIX_DOMAIN_OBJECTS_QUERY, 30 | } from './queries/stix_objects.js'; 31 | import { 32 | ALL_STIX_CORE_RELATIONSHIPS_QUERY, 33 | ALL_STIX_SIGHTING_RELATIONSHIPS_QUERY, 34 | ALL_STIX_REF_RELATIONSHIPS_QUERY, 35 | ALL_STIX_RELATIONSHIPS_QUERY, 36 | } from './queries/relationships.js'; 37 | import { 38 | ALL_CONNECTORS_QUERY, 39 | ALL_STATUS_TEMPLATES_QUERY, 40 | ALL_STATUSES_QUERY, 41 | ALL_SUB_TYPES_QUERY, 42 | ALL_RETENTION_RULES_QUERY, 43 | ALL_BACKGROUND_TASKS_QUERY, 44 | ALL_FEEDS_QUERY, 45 | ALL_TAXII_COLLECTIONS_QUERY, 46 | ALL_STREAM_COLLECTIONS_QUERY, 47 | ALL_RULES_QUERY, 48 | ALL_SYNCHRONIZERS_QUERY, 49 | } from './queries/system.js'; 50 | import { 51 | FILE_BY_ID_QUERY, 52 | ALL_FILES_QUERY, 53 | ALL_INDEXED_FILES_QUERY, 54 | ALL_LOGS_QUERY, 55 | ALL_AUDITS_QUERY, 56 | ALL_ATTRIBUTES_QUERY, 57 | ALL_SCHEMA_ATTRIBUTE_NAMES_QUERY, 58 | ALL_FILTER_KEYS_SCHEMA_QUERY, 59 | } from './queries/metadata.js'; 60 | import { 61 | ALL_MARKING_DEFINITIONS_QUERY, 62 | ALL_LABELS_QUERY, 63 | ALL_EXTERNAL_REFERENCES_QUERY, 64 | ALL_KILL_CHAIN_PHASES_QUERY, 65 | } from './queries/references.js'; 66 | 67 | const OPENCTI_URL = process.env.OPENCTI_URL || 'http://localhost:8080'; 68 | const OPENCTI_TOKEN = process.env.OPENCTI_TOKEN; 69 | 70 | if (!OPENCTI_TOKEN) { 71 | throw new Error('OPENCTI_TOKEN environment variable is required'); 72 | } 73 | 74 | interface OpenCTIResponse { 75 | data: { 76 | stixObjects: Array<{ 77 | id: string; 78 | name?: string; 79 | description?: string; 80 | created_at?: string; 81 | modified_at?: string; 82 | pattern?: string; 83 | valid_from?: string; 84 | valid_until?: string; 85 | x_opencti_score?: number; 86 | [key: string]: any; 87 | }>; 88 | }; 89 | } 90 | 91 | class OpenCTIServer { 92 | private server: Server; 93 | private axiosInstance; 94 | 95 | constructor() { 96 | this.server = new Server( 97 | { 98 | name: 'opencti-server', 99 | version: '0.1.0', 100 | }, 101 | { 102 | capabilities: { 103 | tools: {}, 104 | }, 105 | } 106 | ); 107 | 108 | this.axiosInstance = axios.create({ 109 | baseURL: OPENCTI_URL, 110 | headers: { 111 | 'Authorization': `Bearer ${OPENCTI_TOKEN}`, 112 | 'Content-Type': 'application/json', 113 | }, 114 | }); 115 | 116 | this.setupTools(); 117 | 118 | this.server.onerror = (error) => console.error('[MCP Error]', error); 119 | process.on('SIGINT', async () => { 120 | await this.server.close(); 121 | process.exit(0); 122 | }); 123 | } 124 | 125 | private setupTools() { 126 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 127 | tools: [ 128 | // Reports 129 | { 130 | name: 'get_latest_reports', 131 | description: '獲取最新的OpenCTI報告', 132 | inputSchema: { 133 | type: 'object', 134 | properties: { 135 | first: { 136 | type: 'number', 137 | description: '返回結果數量限制', 138 | default: 10, 139 | }, 140 | }, 141 | }, 142 | }, 143 | { 144 | name: 'get_report_by_id', 145 | description: '根據ID獲取OpenCTI報告', 146 | inputSchema: { 147 | type: 'object', 148 | properties: { 149 | id: { 150 | type: 'string', 151 | description: '報告ID', 152 | }, 153 | }, 154 | required: ['id'], 155 | }, 156 | }, 157 | // Search 158 | { 159 | name: 'search_indicators', 160 | description: '搜尋OpenCTI中的指標', 161 | inputSchema: { 162 | type: 'object', 163 | properties: { 164 | query: { 165 | type: 'string', 166 | description: '搜尋關鍵字', 167 | }, 168 | first: { 169 | type: 'number', 170 | description: '返回結果數量限制', 171 | default: 10, 172 | }, 173 | }, 174 | required: ['query'], 175 | }, 176 | }, 177 | { 178 | name: 'search_malware', 179 | description: '搜尋OpenCTI中的惡意程式', 180 | inputSchema: { 181 | type: 'object', 182 | properties: { 183 | query: { 184 | type: 'string', 185 | description: '搜尋關鍵字', 186 | }, 187 | first: { 188 | type: 'number', 189 | description: '返回結果數量限制', 190 | default: 10, 191 | }, 192 | }, 193 | required: ['query'], 194 | }, 195 | }, 196 | { 197 | name: 'search_threat_actors', 198 | description: '搜尋OpenCTI中的威脅行為者', 199 | inputSchema: { 200 | type: 'object', 201 | properties: { 202 | query: { 203 | type: 'string', 204 | description: '搜尋關鍵字', 205 | }, 206 | first: { 207 | type: 'number', 208 | description: '返回結果數量限制', 209 | default: 10, 210 | }, 211 | }, 212 | required: ['query'], 213 | }, 214 | }, 215 | // Users & Groups 216 | { 217 | name: 'get_user_by_id', 218 | description: '根據ID獲取使用者資訊', 219 | inputSchema: { 220 | type: 'object', 221 | properties: { 222 | id: { 223 | type: 'string', 224 | description: '使用者ID', 225 | }, 226 | }, 227 | required: ['id'], 228 | }, 229 | }, 230 | { 231 | name: 'list_users', 232 | description: '列出所有使用者', 233 | inputSchema: { 234 | type: 'object', 235 | properties: {}, 236 | }, 237 | }, 238 | { 239 | name: 'list_groups', 240 | description: '列出所有群組', 241 | inputSchema: { 242 | type: 'object', 243 | properties: { 244 | first: { 245 | type: 'number', 246 | description: '返回結果數量限制', 247 | default: 10, 248 | }, 249 | }, 250 | }, 251 | }, 252 | // STIX Objects 253 | { 254 | name: 'list_attack_patterns', 255 | description: '列出所有攻擊模式', 256 | inputSchema: { 257 | type: 'object', 258 | properties: { 259 | first: { 260 | type: 'number', 261 | description: '返回結果數量限制', 262 | default: 10, 263 | }, 264 | }, 265 | }, 266 | }, 267 | { 268 | name: 'get_campaign_by_name', 269 | description: '根據名稱獲取行動資訊', 270 | inputSchema: { 271 | type: 'object', 272 | properties: { 273 | name: { 274 | type: 'string', 275 | description: '行動名稱', 276 | }, 277 | }, 278 | required: ['name'], 279 | }, 280 | }, 281 | // System 282 | { 283 | name: 'list_connectors', 284 | description: '列出所有連接器', 285 | inputSchema: { 286 | type: 'object', 287 | properties: {}, 288 | }, 289 | }, 290 | { 291 | name: 'list_status_templates', 292 | description: '列出所有狀態模板', 293 | inputSchema: { 294 | type: 'object', 295 | properties: {}, 296 | }, 297 | }, 298 | // Files 299 | { 300 | name: 'get_file_by_id', 301 | description: '根據ID獲取檔案資訊', 302 | inputSchema: { 303 | type: 'object', 304 | properties: { 305 | id: { 306 | type: 'string', 307 | description: '檔案ID', 308 | }, 309 | }, 310 | required: ['id'], 311 | }, 312 | }, 313 | { 314 | name: 'list_files', 315 | description: '列出所有檔案', 316 | inputSchema: { 317 | type: 'object', 318 | properties: {}, 319 | }, 320 | }, 321 | // References 322 | { 323 | name: 'list_marking_definitions', 324 | description: '列出所有標記定義', 325 | inputSchema: { 326 | type: 'object', 327 | properties: {}, 328 | }, 329 | }, 330 | { 331 | name: 'list_labels', 332 | description: '列出所有標籤', 333 | inputSchema: { 334 | type: 'object', 335 | properties: {}, 336 | }, 337 | }, 338 | ], 339 | })); 340 | 341 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 342 | try { 343 | let query = ''; 344 | let variables: any = {}; 345 | 346 | switch (request.params.name) { 347 | // Reports 348 | case 'get_latest_reports': 349 | query = LATEST_REPORTS_QUERY; 350 | variables = { 351 | first: typeof request.params.arguments?.first === 'number' ? request.params.arguments.first : 10, 352 | }; 353 | break; 354 | 355 | case 'get_report_by_id': 356 | if (!request.params.arguments?.id) { 357 | throw new McpError(ErrorCode.InvalidParams, 'Report ID is required'); 358 | } 359 | query = REPORT_BY_ID_QUERY; 360 | variables = { id: request.params.arguments.id }; 361 | break; 362 | 363 | // Search 364 | case 'search_indicators': 365 | if (!request.params.arguments?.query) { 366 | throw new McpError(ErrorCode.InvalidParams, 'Query parameter is required'); 367 | } 368 | query = SEARCH_INDICATORS_QUERY; 369 | variables = { 370 | search: request.params.arguments.query, 371 | first: typeof request.params.arguments.first === 'number' ? request.params.arguments.first : 10, 372 | }; 373 | break; 374 | 375 | case 'search_malware': 376 | if (!request.params.arguments?.query) { 377 | throw new McpError(ErrorCode.InvalidParams, 'Query parameter is required'); 378 | } 379 | query = SEARCH_MALWARE_QUERY; 380 | variables = { 381 | search: request.params.arguments.query, 382 | first: typeof request.params.arguments.first === 'number' ? request.params.arguments.first : 10, 383 | }; 384 | break; 385 | 386 | case 'search_threat_actors': 387 | if (!request.params.arguments?.query) { 388 | throw new McpError(ErrorCode.InvalidParams, 'Query parameter is required'); 389 | } 390 | query = SEARCH_THREAT_ACTORS_QUERY; 391 | variables = { 392 | search: request.params.arguments.query, 393 | first: typeof request.params.arguments.first === 'number' ? request.params.arguments.first : 10, 394 | }; 395 | break; 396 | 397 | // Users & Groups 398 | case 'get_user_by_id': 399 | if (!request.params.arguments?.id) { 400 | throw new McpError(ErrorCode.InvalidParams, 'User ID is required'); 401 | } 402 | query = USER_BY_ID_QUERY; 403 | variables = { id: request.params.arguments.id }; 404 | break; 405 | 406 | case 'list_users': 407 | query = ALL_USERS_QUERY; 408 | break; 409 | 410 | case 'list_groups': 411 | query = ALL_GROUPS_QUERY; 412 | variables = { 413 | first: typeof request.params.arguments?.first === 'number' ? request.params.arguments.first : 10, 414 | }; 415 | break; 416 | 417 | // STIX Objects 418 | case 'list_attack_patterns': 419 | query = ALL_ATTACK_PATTERNS_QUERY; 420 | variables = { 421 | first: typeof request.params.arguments?.first === 'number' ? request.params.arguments.first : 10, 422 | }; 423 | break; 424 | 425 | case 'get_campaign_by_name': 426 | if (!request.params.arguments?.name) { 427 | throw new McpError(ErrorCode.InvalidParams, 'Campaign name is required'); 428 | } 429 | query = CAMPAIGN_BY_NAME_QUERY; 430 | variables = { name: request.params.arguments.name }; 431 | break; 432 | 433 | // System 434 | case 'list_connectors': 435 | query = ALL_CONNECTORS_QUERY; 436 | break; 437 | 438 | case 'list_status_templates': 439 | query = ALL_STATUS_TEMPLATES_QUERY; 440 | break; 441 | 442 | // Files 443 | case 'get_file_by_id': 444 | if (!request.params.arguments?.id) { 445 | throw new McpError(ErrorCode.InvalidParams, 'File ID is required'); 446 | } 447 | query = FILE_BY_ID_QUERY; 448 | variables = { id: request.params.arguments.id }; 449 | break; 450 | 451 | case 'list_files': 452 | query = ALL_FILES_QUERY; 453 | break; 454 | 455 | // References 456 | case 'list_marking_definitions': 457 | query = ALL_MARKING_DEFINITIONS_QUERY; 458 | break; 459 | 460 | case 'list_labels': 461 | query = ALL_LABELS_QUERY; 462 | break; 463 | 464 | default: 465 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); 466 | } 467 | 468 | const response = await this.axiosInstance.post('/graphql', { 469 | query, 470 | variables, 471 | }); 472 | 473 | console.error('OpenCTI Response:', JSON.stringify(response.data, null, 2)); 474 | 475 | if (!response.data?.data) { 476 | throw new McpError( 477 | ErrorCode.InternalError, 478 | `Invalid response format from OpenCTI: ${JSON.stringify(response.data)}` 479 | ); 480 | } 481 | 482 | let formattedResponse; 483 | 484 | switch (request.params.name) { 485 | case 'get_latest_reports': 486 | formattedResponse = response.data.data.reports.edges.map((edge: any) => ({ 487 | id: edge.node.id, 488 | name: edge.node.name || 'Unnamed', 489 | description: edge.node.description || '', 490 | content: edge.node.content || '', 491 | published: edge.node.published, 492 | confidence: edge.node.confidence, 493 | created: edge.node.created, 494 | modified: edge.node.modified, 495 | reportTypes: edge.node.report_types || [], 496 | })); 497 | break; 498 | 499 | case 'get_report_by_id': 500 | formattedResponse = { 501 | ...response.data.data.report, 502 | name: response.data.data.report.name || 'Unnamed', 503 | description: response.data.data.report.description || '', 504 | }; 505 | break; 506 | 507 | case 'search_indicators': 508 | case 'search_malware': 509 | case 'search_threat_actors': 510 | formattedResponse = response.data.data.stixCoreObjects.edges.map((edge: any) => ({ 511 | id: edge.node.id, 512 | name: edge.node.name || 'Unnamed', 513 | description: edge.node.description || '', 514 | created: edge.node.created, 515 | modified: edge.node.modified, 516 | type: edge.node.malware_types?.join(', ') || edge.node.threat_actor_types?.join(', ') || '', 517 | family: edge.node.is_family ? 'Yes' : 'No', 518 | firstSeen: edge.node.first_seen || '', 519 | lastSeen: edge.node.last_seen || '', 520 | pattern: edge.node.pattern || '', 521 | validFrom: edge.node.valid_from || '', 522 | validUntil: edge.node.valid_until || '', 523 | score: edge.node.x_opencti_score, 524 | })); 525 | break; 526 | 527 | case 'get_user_by_id': 528 | formattedResponse = { 529 | ...response.data.data.user, 530 | name: response.data.data.user.name || 'Unnamed', 531 | }; 532 | break; 533 | 534 | case 'list_users': 535 | formattedResponse = response.data.data.users.edges.map((edge: any) => ({ 536 | id: edge.node.id, 537 | name: edge.node.name || 'Unnamed', 538 | email: edge.node.user_email, 539 | firstname: edge.node.firstname, 540 | lastname: edge.node.lastname, 541 | created: edge.node.created_at, 542 | modified: edge.node.updated_at, 543 | })); 544 | break; 545 | 546 | case 'list_groups': 547 | formattedResponse = response.data.data.groups.edges.map((edge: any) => ({ 548 | id: edge.node.id, 549 | name: edge.node.name || 'Unnamed', 550 | description: edge.node.description || '', 551 | members: edge.node.members?.edges?.map((memberEdge: any) => ({ 552 | id: memberEdge.node.id, 553 | name: memberEdge.node.name, 554 | email: memberEdge.node.user_email, 555 | })) || [], 556 | })); 557 | break; 558 | 559 | case 'list_attack_patterns': 560 | formattedResponse = response.data.data.attackPatterns.edges.map((edge: any) => ({ 561 | id: edge.node.id, 562 | name: edge.node.name || 'Unnamed', 563 | description: edge.node.description || '', 564 | created: edge.node.created_at, 565 | modified: edge.node.updated_at, 566 | killChainPhases: edge.node.killChainPhases?.edges?.map((phaseEdge: any) => ({ 567 | id: phaseEdge.node.id, 568 | name: phaseEdge.node.phase_name, 569 | })) || [], 570 | })); 571 | break; 572 | 573 | case 'list_connectors': 574 | formattedResponse = response.data.data.connectors.map((connector: any) => ({ 575 | id: connector.id, 576 | name: connector.name || 'Unnamed', 577 | type: connector.connector_type, 578 | scope: connector.connector_scope, 579 | state: connector.connector_state, 580 | active: connector.active, 581 | updated: connector.updated_at, 582 | created: connector.created_at, 583 | })); 584 | break; 585 | 586 | case 'list_status_templates': 587 | formattedResponse = response.data.data.statusTemplates.edges.map((edge: any) => ({ 588 | id: edge.node.id, 589 | name: edge.node.name || 'Unnamed', 590 | color: edge.node.color, 591 | usages: edge.node.usages, 592 | })); 593 | break; 594 | 595 | case 'list_marking_definitions': 596 | formattedResponse = response.data.data.markingDefinitions.edges.map((edge: any) => ({ 597 | id: edge.node.id, 598 | definition: edge.node.definition, 599 | color: edge.node.x_opencti_color, 600 | order: edge.node.x_opencti_order, 601 | })); 602 | break; 603 | 604 | case 'list_labels': 605 | formattedResponse = response.data.data.labels.edges.map((edge: any) => ({ 606 | id: edge.node.id, 607 | value: edge.node.value, 608 | color: edge.node.color, 609 | })); 610 | break; 611 | 612 | default: 613 | formattedResponse = response.data.data; 614 | } 615 | 616 | return { 617 | content: [{ 618 | type: 'text', 619 | text: JSON.stringify(formattedResponse, null, 2) 620 | }] 621 | }; 622 | } catch (error) { 623 | if (axios.isAxiosError(error)) { 624 | console.error('Axios Error:', error.response?.data); 625 | return { 626 | content: [{ 627 | type: 'text', 628 | text: `OpenCTI API error: ${JSON.stringify(error.response?.data) || error.message}` 629 | }], 630 | isError: true, 631 | }; 632 | } 633 | throw error; 634 | } 635 | }); 636 | } 637 | 638 | async run() { 639 | const transport = new StdioServerTransport(); 640 | await this.server.connect(transport); 641 | console.error('OpenCTI MCP server running on stdio'); 642 | } 643 | } 644 | 645 | const server = new OpenCTIServer(); 646 | server.run().catch(console.error); 647 | ```