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 |
```