second-opinion-mcp 0.3.0__py3-none-any.whl → 0.3.2__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.
@@ -1,6 +1,6 @@
1
1
  """Second Opinion MCP Server - Multi-model AI analysis for Claude."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.3.2"
4
4
  __author__ = "MarvinFS"
5
5
 
6
6
  from second_opinion_mcp.server import mcp, second_opinion, challenge, code_review, consensus, review_synthesis
second_opinion_mcp/cli.py CHANGED
@@ -11,6 +11,7 @@ except ImportError:
11
11
  sys.exit(1)
12
12
 
13
13
  import keyring
14
+ from keyring.errors import NoKeyringError
14
15
 
15
16
  # Provider configurations
16
17
  PROVIDERS = {
@@ -19,7 +20,7 @@ PROVIDERS = {
19
20
  "description": "DeepSeek Reasoner with chain-of-thought reasoning",
20
21
  "keyring_service": "second-opinion",
21
22
  "keyring_name": "deepseek",
22
- "key_hint": "Get your key at https://platform.deepseek.com",
23
+ "key_hint": "https://platform.deepseek.com",
23
24
  "key_prefix": "sk-",
24
25
  },
25
26
  "moonshot": {
@@ -27,7 +28,7 @@ PROVIDERS = {
27
28
  "description": "Moonshot AI's Kimi with thinking mode",
28
29
  "keyring_service": "second-opinion",
29
30
  "keyring_name": "moonshot",
30
- "key_hint": "Get your key at https://platform.moonshot.cn",
31
+ "key_hint": "https://platform.moonshot.cn",
31
32
  "key_prefix": None,
32
33
  },
33
34
  "openrouter": {
@@ -35,7 +36,7 @@ PROVIDERS = {
35
36
  "description": "Access 300+ models via unified API",
36
37
  "keyring_service": "second-opinion",
37
38
  "keyring_name": "openrouter",
38
- "key_hint": "Get your key at https://openrouter.ai/keys",
39
+ "key_hint": "https://openrouter.ai/keys",
39
40
  "key_prefix": "sk-or-",
40
41
  },
41
42
  }
@@ -51,6 +52,122 @@ custom_style = Style([
51
52
  ])
52
53
 
53
54
 
55
+ def check_keyring_available() -> tuple[bool, str]:
56
+ """Check if keyring backend is available and working."""
57
+ try:
58
+ # Try to access keyring - this will fail if no backend
59
+ backend = keyring.get_keyring()
60
+ backend_name = str(type(backend).__name__)
61
+
62
+ # Check for null/fail backends
63
+ if "Null" in backend_name or "Fail" in backend_name:
64
+ return False, backend_name
65
+
66
+ # Try a test write/read/delete to verify it works
67
+ test_service = "second-opinion-test"
68
+ test_key = "connectivity-check"
69
+ try:
70
+ keyring.set_password(test_service, test_key, "test")
71
+ result = keyring.get_password(test_service, test_key)
72
+ keyring.delete_password(test_service, test_key)
73
+ if result != "test":
74
+ return False, backend_name
75
+ except Exception:
76
+ return False, backend_name
77
+
78
+ return True, backend_name
79
+ except NoKeyringError:
80
+ return False, "NoKeyringError"
81
+ except Exception as e:
82
+ return False, str(e)
83
+
84
+
85
+ def print_keyring_setup_instructions():
86
+ """Print instructions for setting up keyring on different platforms."""
87
+ print()
88
+ print("=" * 60)
89
+ print(" Keyring Backend Required")
90
+ print("=" * 60)
91
+ print()
92
+ print("second-opinion-mcp stores API keys securely in your system keyring.")
93
+ print("A keyring backend must be installed and configured first.")
94
+ print()
95
+
96
+ system = platform.system()
97
+
98
+ if system == "Linux":
99
+ print("For Linux, install one of these backends:")
100
+ print()
101
+ print(" Option 1 - Secret Service (GNOME/KDE desktop):")
102
+ print(" sudo apt install gnome-keyring # or kde-wallet")
103
+ print(" pip install secretstorage")
104
+ print()
105
+ print(" Option 2 - Encrypted file (headless/server):")
106
+ print(" pip install keyrings.alt")
107
+ print(" mkdir -p ~/.local/share/python_keyring")
108
+ print(" cat > ~/.local/share/python_keyring/keyringrc.cfg << 'EOF'")
109
+ print(" [backend]")
110
+ print(" default-keyring=keyrings.alt.file.EncryptedKeyring")
111
+ print(" EOF")
112
+ print()
113
+ print(" Then run 'second-opinion-mcp setup' again.")
114
+
115
+ elif system == "Darwin":
116
+ print("For macOS, keyring should work automatically with Keychain.")
117
+ print("If you see this error, try:")
118
+ print()
119
+ print(" pip install keyring --upgrade")
120
+ print()
121
+ print("Then run 'second-opinion-mcp setup' again.")
122
+
123
+ else:
124
+ print("For Windows, keyring should work automatically with Credential Manager.")
125
+ print("If you see this error, try:")
126
+ print()
127
+ print(" pip install keyring --upgrade")
128
+ print()
129
+ print("Then run 'second-opinion-mcp setup' again.")
130
+
131
+ print()
132
+
133
+
134
+ def print_manual_setup_instructions(providers: list[str] | None = None):
135
+ """Print instructions for manual keyring setup."""
136
+ print()
137
+ print("=" * 60)
138
+ print(" Manual API Key Setup")
139
+ print("=" * 60)
140
+ print()
141
+ print("IMPORTANT: You must configure at least 2 providers for the")
142
+ print("'consensus' tool to work. Single-provider mode is limited.")
143
+ print()
144
+ print("Run these commands to store your API keys:")
145
+ print()
146
+
147
+ target_providers = providers if providers else list(PROVIDERS.keys())
148
+
149
+ for provider in target_providers:
150
+ config = PROVIDERS[provider]
151
+ print(f" # {config['name']} - Get key at {config['key_hint']}")
152
+ print(f" python -c \"import keyring; keyring.set_password('{config['keyring_service']}', '{config['keyring_name']}', 'YOUR_API_KEY')\"")
153
+ print()
154
+
155
+ print("To verify your keys are stored:")
156
+ print()
157
+ print(" python -c \"import keyring; print('deepseek:', bool(keyring.get_password('second-opinion', 'deepseek'))); print('moonshot:', bool(keyring.get_password('second-opinion', 'moonshot')))\"")
158
+ print()
159
+ print("After configuring keys, register with Claude Code:")
160
+ print()
161
+
162
+ if providers and len(providers) >= 1:
163
+ providers_str = ",".join(providers)
164
+ else:
165
+ providers_str = "deepseek,moonshot"
166
+
167
+ print(f" claude mcp add -s user second-opinion -e SECOND_OPINION_PROVIDERS=\"{providers_str}\" -- second-opinion-mcp")
168
+ print()
169
+
170
+
54
171
  def check_existing_keys() -> dict[str, bool]:
55
172
  """Check which providers already have API keys configured."""
56
173
  existing = {}
@@ -63,15 +180,19 @@ def check_existing_keys() -> dict[str, bool]:
63
180
  return existing
64
181
 
65
182
 
66
- def store_api_key(provider: str, key: str) -> bool:
67
- """Store API key in system keyring."""
183
+ def count_configured_providers() -> int:
184
+ """Count how many providers have API keys configured."""
185
+ return sum(1 for v in check_existing_keys().values() if v)
186
+
187
+
188
+ def store_api_key(provider: str, key: str) -> tuple[bool, str]:
189
+ """Store API key in system keyring. Returns (success, error_message)."""
68
190
  config = PROVIDERS[provider]
69
191
  try:
70
192
  keyring.set_password(config["keyring_service"], config["keyring_name"], key)
71
- return True
193
+ return True, ""
72
194
  except Exception as e:
73
- print(f"Error storing key: {e}")
74
- return False
195
+ return False, str(e)
75
196
 
76
197
 
77
198
  def validate_api_key(provider: str, key: str) -> tuple[bool, str]:
@@ -94,11 +215,7 @@ def validate_api_key(provider: str, key: str) -> tuple[bool, str]:
94
215
  def get_registration_command(selected_providers: list[str]) -> str:
95
216
  """Generate the registration command for Claude Code CLI."""
96
217
  providers_env = ",".join(selected_providers)
97
-
98
- if platform.system() == "Windows":
99
- return f'''claude mcp add -s user second-opinion -e SECOND_OPINION_PROVIDERS="{providers_env}" -- second-opinion-mcp'''
100
- else:
101
- return f'''claude mcp add -s user second-opinion -e SECOND_OPINION_PROVIDERS="{providers_env}" -- second-opinion-mcp'''
218
+ return f'claude mcp add -s user second-opinion -e SECOND_OPINION_PROVIDERS="{providers_env}" -- second-opinion-mcp'
102
219
 
103
220
 
104
221
  def get_desktop_config(selected_providers: list[str]) -> str:
@@ -124,29 +241,92 @@ def setup_wizard():
124
241
  print(" Second Opinion MCP - Setup Wizard")
125
242
  print("=" * 60)
126
243
  print()
244
+
245
+ # Check keyring availability first
246
+ keyring_ok, backend_info = check_keyring_available()
247
+
248
+ if not keyring_ok:
249
+ print(f"Keyring backend not available: {backend_info}")
250
+ print_keyring_setup_instructions()
251
+ print()
252
+ print("Alternatively, choose 'Manual setup' to get CLI commands.")
253
+ print()
254
+
255
+ choice = questionary.select(
256
+ "How would you like to proceed?",
257
+ choices=[
258
+ questionary.Choice("Manual setup (show CLI commands)", value="manual"),
259
+ questionary.Choice("Exit and fix keyring first", value="exit"),
260
+ ],
261
+ style=custom_style,
262
+ ).ask()
263
+
264
+ if choice == "manual" or choice is None:
265
+ print_manual_setup_instructions()
266
+ return
267
+
127
268
  print("This wizard will help you configure API keys for AI providers.")
128
269
  print("Keys are stored securely in your system keyring.")
129
270
  print()
130
271
 
131
272
  # Check existing configuration
132
273
  existing = check_existing_keys()
133
- has_existing = any(existing.values())
274
+ configured_count = sum(1 for v in existing.values() if v)
134
275
 
135
- if has_existing:
276
+ if configured_count > 0:
136
277
  configured = [p for p, has_key in existing.items() if has_key]
137
278
  print(f"Already configured: {', '.join(configured)}")
279
+ if configured_count < 2:
280
+ print("Note: At least 2 providers required for 'consensus' tool.")
138
281
  print()
139
282
 
140
- # Provider selection
283
+ # Setup method selection
284
+ setup_method = questionary.select(
285
+ "Setup method:",
286
+ choices=[
287
+ questionary.Choice("Interactive (enter keys now)", value="interactive"),
288
+ questionary.Choice("Manual (show CLI commands)", value="manual"),
289
+ ],
290
+ style=custom_style,
291
+ ).ask()
292
+
293
+ if setup_method is None:
294
+ print("Setup cancelled.")
295
+ return
296
+
297
+ if setup_method == "manual":
298
+ # Ask which providers they want to configure
299
+ choices = [
300
+ questionary.Choice(
301
+ f"{config['name']}" + (" (configured)" if existing.get(provider) else ""),
302
+ value=provider,
303
+ checked=not existing.get(provider) # Pre-check unconfigured ones
304
+ )
305
+ for provider, config in PROVIDERS.items()
306
+ ]
307
+
308
+ selected = questionary.checkbox(
309
+ "Select providers to configure:",
310
+ choices=choices,
311
+ style=custom_style,
312
+ ).ask()
313
+
314
+ if not selected:
315
+ selected = list(PROVIDERS.keys())
316
+
317
+ print_manual_setup_instructions(selected)
318
+ return
319
+
320
+ # Interactive setup
141
321
  choices = []
142
322
  for provider, config in PROVIDERS.items():
143
323
  label = config["name"]
144
324
  if existing.get(provider):
145
325
  label += " (configured)"
146
- choices.append(questionary.Choice(label, value=provider, checked=existing.get(provider, False)))
326
+ choices.append(questionary.Choice(label, value=provider, checked=not existing.get(provider)))
147
327
 
148
328
  selected = questionary.checkbox(
149
- "Select providers to configure (at least 2 recommended):",
329
+ "Select providers to configure (at least 2 required for consensus):",
150
330
  choices=choices,
151
331
  style=custom_style,
152
332
  validate=lambda x: len(x) >= 1 or "Select at least one provider",
@@ -156,20 +336,24 @@ def setup_wizard():
156
336
  print("Setup cancelled.")
157
337
  return
158
338
 
159
- if len(selected) < 2:
339
+ # Warn about single provider
340
+ total_will_have = len([p for p in selected if not existing.get(p)]) + configured_count
341
+ if total_will_have < 2 and len(selected) < 2:
160
342
  print()
161
- print("Note: Using 2+ providers enables the 'consensus' tool for multi-model debates.")
343
+ print("WARNING: You need at least 2 providers for the 'consensus' tool.")
344
+ print("With only 1 provider, only basic tools will be available.")
162
345
  proceed = questionary.confirm(
163
- "Continue with single provider?",
164
- default=True,
346
+ "Continue anyway?",
347
+ default=False,
165
348
  style=custom_style,
166
349
  ).ask()
167
350
  if not proceed:
168
- return setup_wizard() # Restart
351
+ return setup_wizard()
169
352
 
170
353
  print()
171
354
 
172
355
  # Configure API keys
356
+ keyring_error_shown = False
173
357
  for provider in selected:
174
358
  config = PROVIDERS[provider]
175
359
 
@@ -183,15 +367,15 @@ def setup_wizard():
183
367
  continue
184
368
 
185
369
  print(f"\n{config['name']}")
186
- print(f" {config['key_hint']}")
370
+ print(f" Get your key at: {config['key_hint']}")
187
371
 
188
372
  while True:
189
373
  key = questionary.password(
190
- f" Enter API key:",
374
+ " Enter API key:",
191
375
  style=custom_style,
192
376
  ).ask()
193
377
 
194
- if key is None: # User cancelled
378
+ if key is None:
195
379
  print(" Skipped.")
196
380
  break
197
381
 
@@ -203,44 +387,55 @@ def setup_wizard():
203
387
  break
204
388
  continue
205
389
 
206
- if store_api_key(provider, key.strip()):
390
+ success, store_error = store_api_key(provider, key.strip())
391
+ if success:
207
392
  print(" Key stored successfully.")
208
- break
393
+ break
394
+ else:
395
+ print(f" Error storing key: {store_error}")
396
+ if not keyring_error_shown:
397
+ keyring_error_shown = True
398
+ print()
399
+ print(" Keyring storage failed. You may need to configure a backend.")
400
+ show_manual = questionary.confirm(
401
+ " Show manual setup instructions?",
402
+ default=True,
403
+ style=custom_style,
404
+ ).ask()
405
+ if show_manual:
406
+ print_manual_setup_instructions(selected)
407
+ return
408
+ break
409
+
410
+ # Check final configuration
411
+ final_existing = check_existing_keys()
412
+ configured = [p for p, has_key in final_existing.items() if has_key]
209
413
 
210
- # Generate registration commands
211
414
  print()
212
415
  print("=" * 60)
213
416
  print(" Setup Complete!")
214
417
  print("=" * 60)
215
418
  print()
216
419
 
217
- # Filter to providers that now have keys
218
- configured = []
219
- for provider in selected:
220
- try:
221
- key = keyring.get_password(
222
- PROVIDERS[provider]["keyring_service"],
223
- PROVIDERS[provider]["keyring_name"]
224
- )
225
- if key:
226
- configured.append(provider)
227
- except Exception:
228
- pass
229
-
230
420
  if not configured:
231
- print("No API keys were configured. Run 'second-opinion-mcp setup' to try again.")
421
+ print("No API keys were configured.")
422
+ print("Run 'second-opinion-mcp setup' to try again.")
232
423
  return
233
424
 
234
425
  print(f"Configured providers: {', '.join(configured)}")
235
- print()
236
426
 
237
- # Show registration command
238
- print("For Claude Code CLI, run:")
427
+ if len(configured) < 2:
428
+ print()
429
+ print("WARNING: Only 1 provider configured. The 'consensus' tool requires 2+.")
430
+ print("Run 'second-opinion-mcp setup' to add more providers.")
431
+
432
+ print()
433
+ print("Register with Claude Code CLI:")
239
434
  print()
240
435
  print(f" {get_registration_command(configured)}")
241
436
  print()
242
437
 
243
- # Show desktop config
438
+ # Show desktop config option
244
439
  show_desktop = questionary.confirm(
245
440
  "Show Claude Desktop configuration?",
246
441
  default=False,
@@ -264,8 +459,6 @@ def setup_wizard():
264
459
  print("Test the server with:")
265
460
  print(" second-opinion-mcp")
266
461
  print()
267
- print("The server will start and wait for input. Press Ctrl+C to stop.")
268
- print()
269
462
 
270
463
 
271
464
  if __name__ == "__main__":
@@ -1523,13 +1523,121 @@ def _sync_cleanup():
1523
1523
  atexit.register(_sync_cleanup)
1524
1524
 
1525
1525
 
1526
+ def _check_provider_keys() -> tuple[list[str], list[str]]:
1527
+ """Check which enabled providers have API keys configured.
1528
+
1529
+ Returns:
1530
+ (configured_providers, missing_providers) tuple
1531
+ """
1532
+ configured = []
1533
+ missing = []
1534
+
1535
+ for provider, config in MODELS.items():
1536
+ if not config.get("enabled"):
1537
+ continue
1538
+
1539
+ try:
1540
+ key = keyring.get_password(config["keyring_service"], config["keyring_name"])
1541
+ if key:
1542
+ configured.append(provider)
1543
+ else:
1544
+ missing.append(provider)
1545
+ except Exception:
1546
+ missing.append(provider)
1547
+
1548
+ return configured, missing
1549
+
1550
+
1551
+ def _validate_startup_requirements() -> tuple[bool, str]:
1552
+ """Validate that minimum requirements are met to run the server.
1553
+
1554
+ Returns:
1555
+ (ok, error_message) tuple. If ok is False, server should not start.
1556
+ """
1557
+ configured, missing = _check_provider_keys()
1558
+
1559
+ if len(configured) == 0:
1560
+ return False, (
1561
+ "No API keys configured. Run 'second-opinion-mcp setup' to configure providers.\n\n"
1562
+ "Or manually configure keys:\n"
1563
+ " python -c \"import keyring; keyring.set_password('second-opinion', 'deepseek', 'YOUR_KEY')\"\n"
1564
+ " python -c \"import keyring; keyring.set_password('second-opinion', 'moonshot', 'YOUR_KEY')\"\n"
1565
+ )
1566
+
1567
+ if len(configured) == 1:
1568
+ return False, (
1569
+ f"Only 1 provider configured ({configured[0]}). At least 2 providers required.\n\n"
1570
+ "The 'consensus' tool requires multiple models to debate.\n"
1571
+ "Run 'second-opinion-mcp setup' to add another provider.\n\n"
1572
+ f"Missing: {', '.join(missing) if missing else 'none enabled'}\n"
1573
+ )
1574
+
1575
+ return True, ""
1576
+
1577
+
1578
+ def _check_for_updates() -> tuple[str | None, str | None]:
1579
+ """Check PyPI for newer version of second-opinion-mcp.
1580
+
1581
+ Returns:
1582
+ (latest_version, current_version) if update available, (None, None) otherwise.
1583
+ """
1584
+ import urllib.request
1585
+ import json
1586
+
1587
+ try:
1588
+ from second_opinion_mcp import __version__ as current_version
1589
+ except ImportError:
1590
+ current_version = "0.0.0"
1591
+
1592
+ try:
1593
+ # Quick timeout to not delay startup
1594
+ url = "https://pypi.org/pypi/second-opinion-mcp/json"
1595
+ req = urllib.request.Request(url, headers={"User-Agent": "second-opinion-mcp"})
1596
+ with urllib.request.urlopen(req, timeout=3) as response:
1597
+ data = json.loads(response.read().decode())
1598
+ latest_version = data.get("info", {}).get("version", "0.0.0")
1599
+
1600
+ # Simple version comparison (works for semver)
1601
+ def parse_version(v: str) -> tuple[int, ...]:
1602
+ return tuple(int(x) for x in v.split(".")[:3] if x.isdigit())
1603
+
1604
+ if parse_version(latest_version) > parse_version(current_version):
1605
+ return latest_version, current_version
1606
+
1607
+ except Exception:
1608
+ # Don't fail startup on update check errors
1609
+ pass
1610
+
1611
+ return None, None
1612
+
1613
+
1526
1614
  def main():
1527
1615
  """Run the MCP server with stdio transport."""
1616
+ import sys
1617
+
1528
1618
  # Configure logging when run directly (not when imported as library)
1529
1619
  logging.basicConfig(
1530
1620
  level=logging.INFO,
1531
1621
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
1532
1622
  )
1623
+
1624
+ # Check for updates (non-blocking, quick timeout)
1625
+ latest, current = _check_for_updates()
1626
+ if latest:
1627
+ print(f"Update available: {current} -> {latest}", file=sys.stderr)
1628
+ print("Run: pipx upgrade second-opinion-mcp", file=sys.stderr)
1629
+ print(file=sys.stderr)
1630
+
1631
+ # Validate minimum requirements
1632
+ ok, error = _validate_startup_requirements()
1633
+ if not ok:
1634
+ print(f"Error: {error}", file=sys.stderr)
1635
+ sys.exit(1)
1636
+
1637
+ # Log configured providers
1638
+ configured, _ = _check_provider_keys()
1639
+ logger.info("Starting with providers: %s", ", ".join(configured))
1640
+
1533
1641
  mcp.run(transport="stdio")
1534
1642
 
1535
1643
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: second-opinion-mcp
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Multi-model AI analysis for Claude - get second opinions from DeepSeek, Kimi, and OpenRouter
5
5
  Project-URL: Homepage, https://github.com/MarvinFS/second-opinion-mcp
6
6
  Project-URL: Repository, https://github.com/MarvinFS/second-opinion-mcp
@@ -279,6 +279,25 @@ Use code_review with focus_areas=["security", "error-handling", "performance"]
279
279
  Use code_review on directory="./src" with save_to="./docs/reviews"
280
280
  ```
281
281
 
282
+ ## Updating
283
+
284
+ The server checks for updates on startup and notifies you if a new version is available.
285
+
286
+ To update manually:
287
+
288
+ ```bash
289
+ pipx upgrade second-opinion-mcp
290
+ ```
291
+
292
+ Or reinstall:
293
+
294
+ ```bash
295
+ pipx uninstall second-opinion-mcp
296
+ pipx install second-opinion-mcp
297
+ ```
298
+
299
+ Your API keys are preserved in the system keyring across updates.
300
+
282
301
  ## Uninstalling
283
302
 
284
303
  ```bash
@@ -0,0 +1,9 @@
1
+ second_opinion_mcp/__init__.py,sha256=6XCWg4mcxORq2OPxzaC5bVTLmKWEnYok4SWiZ5E6RVs,373
2
+ second_opinion_mcp/__main__.py,sha256=WDv19O6nvIH69GR_DUfXqDt-wm8bgsHNqvPm1LEIKzs,509
3
+ second_opinion_mcp/cli.py,sha256=A88Y0TZQ_B38YliHKzohAOLTZuGdMZzH4bEjyC1Tv_A,15279
4
+ second_opinion_mcp/server.py,sha256=46cHbK0GgL0VBCGI3gmTA1-vQ1jRDdQ6MsCCqWw3LVk,62237
5
+ second_opinion_mcp-0.3.2.dist-info/METADATA,sha256=azoIPNQyVezi-cYoift_BL4RN61qj7PI0Kqqvj2Bosg,8806
6
+ second_opinion_mcp-0.3.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
7
+ second_opinion_mcp-0.3.2.dist-info/entry_points.txt,sha256=-E8BA2gFyU4qW-kJL8SV9Pg1Cc7glOCmraJoZH0PZP8,72
8
+ second_opinion_mcp-0.3.2.dist-info/licenses/LICENSE,sha256=dPx2Jy-Ejearvfh6IlF2PN4Srt-nZW8M4bW5EW7RPAg,1065
9
+ second_opinion_mcp-0.3.2.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- second_opinion_mcp/__init__.py,sha256=NpyIXDQRLwIAokC8JkOznUSIeX_sez7LjviT_Pg_ihM,373
2
- second_opinion_mcp/__main__.py,sha256=WDv19O6nvIH69GR_DUfXqDt-wm8bgsHNqvPm1LEIKzs,509
3
- second_opinion_mcp/cli.py,sha256=waLZH3Av817-3qqu3iG-GZ7YivF3Rp-7h9DzJ5YHgvo,8154
4
- second_opinion_mcp/server.py,sha256=vD2Cl64PfblmDy1oqM1mwF5V8ouMSLFgzttymFq6UxA,58589
5
- second_opinion_mcp-0.3.0.dist-info/METADATA,sha256=OqqHisGgVdSRtaHbsfkxlFc_JPsTahJSH7G6JbfZ6n4,8476
6
- second_opinion_mcp-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
7
- second_opinion_mcp-0.3.0.dist-info/entry_points.txt,sha256=-E8BA2gFyU4qW-kJL8SV9Pg1Cc7glOCmraJoZH0PZP8,72
8
- second_opinion_mcp-0.3.0.dist-info/licenses/LICENSE,sha256=dPx2Jy-Ejearvfh6IlF2PN4Srt-nZW8M4bW5EW7RPAg,1065
9
- second_opinion_mcp-0.3.0.dist-info/RECORD,,