ida-pro-mcp-xjoker 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. ida_pro_mcp/__init__.py +0 -0
  2. ida_pro_mcp/__main__.py +6 -0
  3. ida_pro_mcp/ida_mcp/__init__.py +68 -0
  4. ida_pro_mcp/ida_mcp/api_analysis.py +1296 -0
  5. ida_pro_mcp/ida_mcp/api_core.py +337 -0
  6. ida_pro_mcp/ida_mcp/api_debug.py +617 -0
  7. ida_pro_mcp/ida_mcp/api_memory.py +304 -0
  8. ida_pro_mcp/ida_mcp/api_modify.py +406 -0
  9. ida_pro_mcp/ida_mcp/api_python.py +179 -0
  10. ida_pro_mcp/ida_mcp/api_resources.py +295 -0
  11. ida_pro_mcp/ida_mcp/api_stack.py +167 -0
  12. ida_pro_mcp/ida_mcp/api_types.py +480 -0
  13. ida_pro_mcp/ida_mcp/auth.py +166 -0
  14. ida_pro_mcp/ida_mcp/cache.py +232 -0
  15. ida_pro_mcp/ida_mcp/config.py +228 -0
  16. ida_pro_mcp/ida_mcp/framework.py +547 -0
  17. ida_pro_mcp/ida_mcp/http.py +859 -0
  18. ida_pro_mcp/ida_mcp/port_utils.py +104 -0
  19. ida_pro_mcp/ida_mcp/rpc.py +187 -0
  20. ida_pro_mcp/ida_mcp/server_manager.py +339 -0
  21. ida_pro_mcp/ida_mcp/sync.py +233 -0
  22. ida_pro_mcp/ida_mcp/tests/__init__.py +14 -0
  23. ida_pro_mcp/ida_mcp/tests/test_api_analysis.py +336 -0
  24. ida_pro_mcp/ida_mcp/tests/test_api_core.py +237 -0
  25. ida_pro_mcp/ida_mcp/tests/test_api_memory.py +207 -0
  26. ida_pro_mcp/ida_mcp/tests/test_api_modify.py +123 -0
  27. ida_pro_mcp/ida_mcp/tests/test_api_resources.py +199 -0
  28. ida_pro_mcp/ida_mcp/tests/test_api_stack.py +77 -0
  29. ida_pro_mcp/ida_mcp/tests/test_api_types.py +249 -0
  30. ida_pro_mcp/ida_mcp/ui.py +357 -0
  31. ida_pro_mcp/ida_mcp/utils.py +1186 -0
  32. ida_pro_mcp/ida_mcp/zeromcp/__init__.py +5 -0
  33. ida_pro_mcp/ida_mcp/zeromcp/jsonrpc.py +384 -0
  34. ida_pro_mcp/ida_mcp/zeromcp/mcp.py +883 -0
  35. ida_pro_mcp/ida_mcp.py +186 -0
  36. ida_pro_mcp/idalib_server.py +354 -0
  37. ida_pro_mcp/idalib_session_manager.py +259 -0
  38. ida_pro_mcp/server.py +1060 -0
  39. ida_pro_mcp/test.py +170 -0
  40. ida_pro_mcp_xjoker-1.0.1.dist-info/METADATA +405 -0
  41. ida_pro_mcp_xjoker-1.0.1.dist-info/RECORD +45 -0
  42. ida_pro_mcp_xjoker-1.0.1.dist-info/WHEEL +5 -0
  43. ida_pro_mcp_xjoker-1.0.1.dist-info/entry_points.txt +4 -0
  44. ida_pro_mcp_xjoker-1.0.1.dist-info/licenses/LICENSE +21 -0
  45. ida_pro_mcp_xjoker-1.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,199 @@
1
+ """Tests for api_resources MCP resource functions."""
2
+
3
+ # Import test framework from parent
4
+ from ..framework import (
5
+ test,
6
+ assert_valid_address,
7
+ assert_has_keys,
8
+ assert_non_empty,
9
+ get_any_function,
10
+ )
11
+
12
+ # Import resource functions under test
13
+ from ..api_resources import (
14
+ idb_metadata_resource,
15
+ idb_segments_resource,
16
+ idb_entrypoints_resource,
17
+ cursor_resource,
18
+ selection_resource,
19
+ types_resource,
20
+ structs_resource,
21
+ struct_name_resource,
22
+ import_name_resource,
23
+ export_name_resource,
24
+ xrefs_from_resource,
25
+ )
26
+
27
+ # Import sync module for IDAError
28
+ from ..sync import IDAError
29
+
30
+
31
+ # ============================================================================
32
+ # Tests for idb_metadata_resource
33
+ # ============================================================================
34
+
35
+
36
+ @test()
37
+ def test_resource_idb_metadata():
38
+ """idb_metadata_resource returns IDB metadata"""
39
+ result = idb_metadata_resource()
40
+ assert isinstance(result, dict)
41
+ assert_has_keys(result, "path", "module", "base", "size")
42
+ assert_non_empty(result["path"])
43
+ assert_valid_address(result["base"])
44
+
45
+
46
+ # ============================================================================
47
+ # Tests for idb_segments_resource
48
+ # ============================================================================
49
+
50
+
51
+ @test()
52
+ def test_resource_idb_segments():
53
+ """idb_segments_resource returns segments list"""
54
+ result = idb_segments_resource()
55
+ assert isinstance(result, list)
56
+ if result:
57
+ assert_has_keys(result[0], "name", "start", "end")
58
+
59
+
60
+ # ============================================================================
61
+ # Tests for idb_entrypoints_resource
62
+ # ============================================================================
63
+
64
+
65
+ @test()
66
+ def test_resource_idb_entrypoints():
67
+ """idb_entrypoints_resource returns entry points"""
68
+ result = idb_entrypoints_resource()
69
+ assert isinstance(result, list)
70
+
71
+
72
+ # ============================================================================
73
+ # Tests for cursor_resource
74
+ # ============================================================================
75
+
76
+
77
+ @test()
78
+ def test_resource_cursor():
79
+ """cursor_resource returns cursor info"""
80
+ try:
81
+ result = cursor_resource()
82
+ assert isinstance(result, dict)
83
+ # Should have addr key
84
+ assert_has_keys(result, "addr")
85
+ except IDAError:
86
+ pass # May fail in headless mode
87
+
88
+
89
+ # ============================================================================
90
+ # Tests for selection_resource
91
+ # ============================================================================
92
+
93
+
94
+ @test()
95
+ def test_resource_selection():
96
+ """selection_resource returns selection info"""
97
+ try:
98
+ result = selection_resource()
99
+ assert isinstance(result, dict)
100
+ except IDAError:
101
+ pass # May fail in headless mode
102
+
103
+
104
+ # ============================================================================
105
+ # Tests for types_resource
106
+ # ============================================================================
107
+
108
+
109
+ @test()
110
+ def test_resource_types():
111
+ """types_resource returns local types"""
112
+ result = types_resource()
113
+ assert isinstance(result, list)
114
+
115
+
116
+ # ============================================================================
117
+ # Tests for structs_resource
118
+ # ============================================================================
119
+
120
+
121
+ @test()
122
+ def test_resource_structs():
123
+ """structs_resource returns structures list"""
124
+ result = structs_resource()
125
+ assert isinstance(result, list)
126
+
127
+
128
+ # ============================================================================
129
+ # Tests for struct_name_resource
130
+ # ============================================================================
131
+
132
+
133
+ @test()
134
+ def test_resource_struct_name():
135
+ """struct_name_resource returns structure info"""
136
+ # Try to get a structure (may not exist)
137
+ try:
138
+ result = struct_name_resource("test")
139
+ assert isinstance(result, dict)
140
+ except IDAError:
141
+ pass # Structure may not exist
142
+
143
+
144
+ @test()
145
+ def test_resource_struct_name_not_found():
146
+ """struct_name_resource handles non-existent structure"""
147
+ try:
148
+ result = struct_name_resource("NonExistentStruct12345")
149
+ # Should return error or empty
150
+ except IDAError:
151
+ pass # Expected for non-existent struct
152
+
153
+
154
+ # ============================================================================
155
+ # Tests for import_name_resource
156
+ # ============================================================================
157
+
158
+
159
+ @test()
160
+ def test_resource_import_name():
161
+ """import_name_resource returns import info"""
162
+ # Try to get an import (name depends on binary)
163
+ try:
164
+ result = import_name_resource("printf")
165
+ assert isinstance(result, dict)
166
+ except IDAError:
167
+ pass # Import may not exist in this binary
168
+
169
+
170
+ # ============================================================================
171
+ # Tests for export_name_resource
172
+ # ============================================================================
173
+
174
+
175
+ @test()
176
+ def test_resource_export_name():
177
+ """export_name_resource returns export info"""
178
+ # Try to get main
179
+ try:
180
+ result = export_name_resource("main")
181
+ assert isinstance(result, dict)
182
+ except IDAError:
183
+ pass # Export may not exist
184
+
185
+
186
+ # ============================================================================
187
+ # Tests for xrefs_from_resource
188
+ # ============================================================================
189
+
190
+
191
+ @test()
192
+ def test_resource_xrefs_from():
193
+ """xrefs_from_resource returns cross-references"""
194
+ fn_addr = get_any_function()
195
+ if not fn_addr:
196
+ return
197
+
198
+ result = xrefs_from_resource(fn_addr)
199
+ assert isinstance(result, list)
@@ -0,0 +1,77 @@
1
+ """Tests for api_stack API functions."""
2
+
3
+ # Import test framework from parent
4
+ from ..framework import (
5
+ test,
6
+ assert_has_keys,
7
+ assert_is_list,
8
+ get_any_function,
9
+ get_data_address,
10
+ )
11
+
12
+ # Import functions under test
13
+ from ..api_stack import (
14
+ stack_frame,
15
+ declare_stack,
16
+ delete_stack,
17
+ )
18
+
19
+ # Import sync module for IDAError
20
+
21
+
22
+ # ============================================================================
23
+ # Tests for stack_frame
24
+ # ============================================================================
25
+
26
+
27
+ @test()
28
+ def test_stack_frame():
29
+ """stack_frame returns stack frame info for a function"""
30
+ fn_addr = get_any_function()
31
+ if not fn_addr:
32
+ return
33
+
34
+ result = stack_frame(fn_addr)
35
+ assert_is_list(result, min_length=1)
36
+ r = result[0]
37
+ assert_has_keys(r, "addr", "frame", "error")
38
+
39
+
40
+ @test()
41
+ def test_stack_frame_no_function():
42
+ """stack_frame handles non-function address"""
43
+ data_addr = get_data_address()
44
+ if not data_addr:
45
+ return
46
+
47
+ result = stack_frame(data_addr)
48
+ assert_is_list(result, min_length=1)
49
+ r = result[0]
50
+ # Should have error or null frame
51
+ assert r.get("error") is not None or r.get("frame") is None
52
+
53
+
54
+ # ============================================================================
55
+ # Tests for declare_stack / delete_stack
56
+ # ============================================================================
57
+
58
+
59
+ @test(skip=True) # Skip by default as it modifies the database
60
+ def test_declare_delete_stack():
61
+ """declare_stack and delete_stack work together"""
62
+ fn_addr = get_any_function()
63
+ if not fn_addr:
64
+ return
65
+
66
+ # Try to declare a stack variable
67
+ result = declare_stack(
68
+ {"func": fn_addr, "name": "__test_var__", "offset": -8, "type": "int"}
69
+ )
70
+ assert_is_list(result, min_length=1)
71
+ r = result[0]
72
+ assert_has_keys(r, "func", "error")
73
+
74
+ # If declare succeeded, try to delete
75
+ if r.get("error") is None:
76
+ del_result = delete_stack({"func": fn_addr, "name": "__test_var__"})
77
+ assert_is_list(del_result, min_length=1)
@@ -0,0 +1,249 @@
1
+ """Tests for api_types API functions."""
2
+
3
+ # Import test framework from parent
4
+ from ..framework import (
5
+ test,
6
+ assert_has_keys,
7
+ assert_is_list,
8
+ get_any_function,
9
+ get_first_segment,
10
+ get_data_address,
11
+ get_unmapped_address,
12
+ )
13
+
14
+ # Import functions under test
15
+ from ..api_types import (
16
+ declare_type,
17
+ read_struct,
18
+ search_structs,
19
+ set_type,
20
+ infer_types,
21
+ )
22
+
23
+ # Import sync module for IDAError
24
+
25
+
26
+ # ============================================================================
27
+ # Test Helpers
28
+ # ============================================================================
29
+
30
+
31
+ def create_test_struct(name: str = "__TestStruct__") -> bool:
32
+ """Helper to create a test struct in IDA's type library.
33
+
34
+ This function is idempotent - if the struct already exists, it will return True.
35
+
36
+ Args:
37
+ name: Name of the struct to create
38
+
39
+ Returns:
40
+ True if struct exists or was created successfully, False otherwise
41
+ """
42
+ # First check if struct already exists
43
+ search_result = search_structs(name)
44
+ if search_result and any(s["name"] == name for s in search_result):
45
+ return True # Already exists
46
+
47
+ # Struct doesn't exist, try to create it
48
+ struct_def = f"""
49
+ struct {name} {{
50
+ int field1;
51
+ char field2;
52
+ void* field3;
53
+ }};
54
+ """
55
+ result = declare_type(struct_def)
56
+ if not result:
57
+ return False
58
+
59
+ # Check if declaration succeeded
60
+ r = result[0]
61
+ if r.get("ok"):
62
+ return True
63
+
64
+ # Check if it failed because it already exists
65
+ if r.get("error"):
66
+ # Search again to see if it exists despite the error
67
+ search_result = search_structs(name)
68
+ return search_result and any(s["name"] == name for s in search_result)
69
+
70
+ return False
71
+
72
+
73
+ # ============================================================================
74
+ # Tests for declare_type
75
+ # ============================================================================
76
+
77
+
78
+ @test()
79
+ def test_declare_type():
80
+ """declare_type can add a type declaration"""
81
+ # Try to declare a simple struct
82
+ result = declare_type("struct __test_struct__ { int x; };")
83
+ assert_is_list(result, min_length=1)
84
+ r = result[0]
85
+ assert_has_keys(r, "decl")
86
+ # Should succeed without error
87
+ assert r.get("ok") is not None or r.get("error") is None
88
+
89
+
90
+ # ============================================================================
91
+ # Tests for read_struct
92
+ # ============================================================================
93
+
94
+
95
+ @test()
96
+ def test_read_struct():
97
+ """read_struct reads structure at address"""
98
+ data_addr = get_data_address()
99
+ if not data_addr:
100
+ seg = get_first_segment()
101
+ if not seg:
102
+ return
103
+ data_addr = seg[0]
104
+
105
+ result = read_struct({"addr": data_addr, "struct": "test_struct"})
106
+ assert_is_list(result, min_length=1)
107
+ r = result[0]
108
+ # Should have addr, struct, and either members or error
109
+ assert_has_keys(r, "addr", "struct")
110
+ assert r.get("members") is not None or r.get("error") is not None
111
+
112
+
113
+ @test()
114
+ def test_read_struct_not_found():
115
+ """read_struct handles non-existent struct"""
116
+ seg = get_first_segment()
117
+ if not seg:
118
+ return
119
+
120
+ result = read_struct({"addr": seg[0], "struct": "NonExistentStruct12345"})
121
+ assert_is_list(result, min_length=1)
122
+ r = result[0]
123
+ # Should have error
124
+ assert r.get("error") is not None
125
+
126
+
127
+ @test()
128
+ def test_read_struct_name_resolution():
129
+ """read_struct can resolve named addresses (e.g., function names)"""
130
+ # Create a test struct first
131
+ if not create_test_struct("__NameResolutionTest__"):
132
+ return
133
+
134
+ fn_addr = get_any_function()
135
+ if not fn_addr:
136
+ return
137
+
138
+ # Get the function name
139
+ from ..api_core import lookup_funcs
140
+
141
+ fn_info = lookup_funcs(fn_addr)
142
+ if not fn_info or not fn_info[0].get("fn"):
143
+ return
144
+
145
+ fn_name = fn_info[0]["fn"]["name"]
146
+
147
+ # Use the function name as address (should resolve via get_name_ea)
148
+ result = read_struct({"addr": fn_name, "struct": "__NameResolutionTest__"})
149
+ assert_is_list(result, min_length=1)
150
+ r = result[0]
151
+ # Should either succeed with members or have a specific error
152
+ # (not a "Failed to resolve address" error)
153
+ if r.get("error"):
154
+ # If there's an error, it should be about the struct, not address resolution
155
+ assert "Failed to resolve address" not in r["error"]
156
+
157
+
158
+ @test()
159
+ def test_read_struct_invalid_address():
160
+ """read_struct handles invalid address gracefully"""
161
+ result = read_struct(
162
+ {"addr": "InvalidAddressName123", "struct": "NonExistentStruct"}
163
+ )
164
+ assert_is_list(result, min_length=1)
165
+ r = result[0]
166
+ # Should have error about failed address resolution
167
+ assert r.get("error") is not None
168
+ assert "Failed to resolve address" in r["error"]
169
+
170
+
171
+ # ============================================================================
172
+ # Tests for search_structs
173
+ # ============================================================================
174
+
175
+
176
+ @test()
177
+ def test_search_structs():
178
+ """search_structs can search for structures"""
179
+ result = search_structs("*")
180
+ assert_is_list(result)
181
+ # Check result structure if any structs exist
182
+ if len(result) > 0:
183
+ r = result[0]
184
+ assert_has_keys(r, "name", "size", "cardinality", "is_union", "ordinal")
185
+
186
+
187
+ @test()
188
+ def test_search_structs_pattern():
189
+ """search_structs can filter by pattern"""
190
+ # Test with a pattern that likely won't match anything
191
+ result = search_structs("VeryUnlikelyStructName123*")
192
+ assert_is_list(result)
193
+ # Should return empty list
194
+ assert len(result) == 0
195
+
196
+ # Test with wildcard that should match everything
197
+ result_all = search_structs("*")
198
+ assert_is_list(result_all)
199
+ # Wildcard should return at least as many results as specific pattern
200
+ assert len(result_all) >= len(result)
201
+
202
+
203
+ # ============================================================================
204
+ # Tests for set_type
205
+ # ============================================================================
206
+
207
+
208
+ @test()
209
+ def test_set_type():
210
+ """set_type applies type to address"""
211
+ fn_addr = get_any_function()
212
+ if not fn_addr:
213
+ return
214
+
215
+ result = set_type({"addr": fn_addr, "ty": "int"})
216
+ assert_is_list(result, min_length=1)
217
+ r = result[0]
218
+ # Result has "edit" key containing the input, and optionally "ok" or "error"
219
+ assert_has_keys(r, "edit")
220
+ assert r.get("ok") is not None or r.get("error") is not None
221
+
222
+
223
+ @test()
224
+ def test_set_type_invalid_address():
225
+ """set_type handles invalid address"""
226
+ result = set_type({"addr": get_unmapped_address(), "ty": "int"})
227
+ assert_is_list(result, min_length=1)
228
+ r = result[0]
229
+ # Should have "edit" key and either "ok" or "error"
230
+ assert_has_keys(r, "edit")
231
+ assert r.get("ok") is not None or r.get("error") is not None
232
+
233
+
234
+ # ============================================================================
235
+ # Tests for infer_types
236
+ # ============================================================================
237
+
238
+
239
+ @test()
240
+ def test_infer_types():
241
+ """infer_types infers types for a function"""
242
+ fn_addr = get_any_function()
243
+ if not fn_addr:
244
+ return
245
+
246
+ result = infer_types(fn_addr)
247
+ assert_is_list(result, min_length=1)
248
+ r = result[0]
249
+ assert_has_keys(r, "addr", "error")