pmcp 0.1.0__tar.gz

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 (82) hide show
  1. pmcp-0.1.0/.claude/plans/ticklish-inventing-stearns.md +677 -0
  2. pmcp-0.1.0/.dockerignore +62 -0
  3. pmcp-0.1.0/.env.example +129 -0
  4. pmcp-0.1.0/.github/dependabot.yml +27 -0
  5. pmcp-0.1.0/.github/workflows/docker.yml +57 -0
  6. pmcp-0.1.0/.github/workflows/release.yml +54 -0
  7. pmcp-0.1.0/.github/workflows/test.yml +79 -0
  8. pmcp-0.1.0/.gitignore +56 -0
  9. pmcp-0.1.0/.mcp-gateway/descriptions.yaml +147 -0
  10. pmcp-0.1.0/CHANGELOG.md +37 -0
  11. pmcp-0.1.0/CONTRIBUTING.md +136 -0
  12. pmcp-0.1.0/Dockerfile +38 -0
  13. pmcp-0.1.0/LICENSE +21 -0
  14. pmcp-0.1.0/PKG-INFO +493 -0
  15. pmcp-0.1.0/README.md +450 -0
  16. pmcp-0.1.0/baml_src/capability_match.baml +109 -0
  17. pmcp-0.1.0/baml_src/clients.baml +11 -0
  18. pmcp-0.1.0/baml_src/generators.baml +9 -0
  19. pmcp-0.1.0/baml_src/summarize.baml +71 -0
  20. pmcp-0.1.0/config/.mcp.json.example +15 -0
  21. pmcp-0.1.0/docker-compose.yml +24 -0
  22. pmcp-0.1.0/examples/gateway-only.mcp.json +11 -0
  23. pmcp-0.1.0/examples/gateway-policy.yaml +52 -0
  24. pmcp-0.1.0/examples/sample-backends.mcp.json +20 -0
  25. pmcp-0.1.0/pyproject.toml +94 -0
  26. pmcp-0.1.0/server.json +16 -0
  27. pmcp-0.1.0/src/pmcp/__init__.py +3 -0
  28. pmcp-0.1.0/src/pmcp/__main__.py +6 -0
  29. pmcp-0.1.0/src/pmcp/baml_client/__init__.py +60 -0
  30. pmcp-0.1.0/src/pmcp/baml_client/async_client.py +191 -0
  31. pmcp-0.1.0/src/pmcp/baml_client/config.py +102 -0
  32. pmcp-0.1.0/src/pmcp/baml_client/globals.py +35 -0
  33. pmcp-0.1.0/src/pmcp/baml_client/inlinedbaml.py +22 -0
  34. pmcp-0.1.0/src/pmcp/baml_client/parser.py +59 -0
  35. pmcp-0.1.0/src/pmcp/baml_client/runtime.py +352 -0
  36. pmcp-0.1.0/src/pmcp/baml_client/stream_types.py +72 -0
  37. pmcp-0.1.0/src/pmcp/baml_client/sync_client.py +201 -0
  38. pmcp-0.1.0/src/pmcp/baml_client/tracing.py +22 -0
  39. pmcp-0.1.0/src/pmcp/baml_client/type_builder.py +458 -0
  40. pmcp-0.1.0/src/pmcp/baml_client/type_map.py +44 -0
  41. pmcp-0.1.0/src/pmcp/baml_client/types.py +90 -0
  42. pmcp-0.1.0/src/pmcp/baml_client/watchers.py +45 -0
  43. pmcp-0.1.0/src/pmcp/cli.py +693 -0
  44. pmcp-0.1.0/src/pmcp/client/__init__.py +5 -0
  45. pmcp-0.1.0/src/pmcp/client/manager.py +954 -0
  46. pmcp-0.1.0/src/pmcp/config/__init__.py +5 -0
  47. pmcp-0.1.0/src/pmcp/config/loader.py +205 -0
  48. pmcp-0.1.0/src/pmcp/errors.py +190 -0
  49. pmcp-0.1.0/src/pmcp/manifest/__init__.py +20 -0
  50. pmcp-0.1.0/src/pmcp/manifest/environment.py +149 -0
  51. pmcp-0.1.0/src/pmcp/manifest/installer.py +612 -0
  52. pmcp-0.1.0/src/pmcp/manifest/loader.py +157 -0
  53. pmcp-0.1.0/src/pmcp/manifest/manifest.yaml +452 -0
  54. pmcp-0.1.0/src/pmcp/manifest/matcher.py +225 -0
  55. pmcp-0.1.0/src/pmcp/manifest/refresher.py +440 -0
  56. pmcp-0.1.0/src/pmcp/manifest/version_checker.py +205 -0
  57. pmcp-0.1.0/src/pmcp/policy/__init__.py +5 -0
  58. pmcp-0.1.0/src/pmcp/policy/policy.py +258 -0
  59. pmcp-0.1.0/src/pmcp/server.py +381 -0
  60. pmcp-0.1.0/src/pmcp/summary/__init__.py +5 -0
  61. pmcp-0.1.0/src/pmcp/summary/generator.py +109 -0
  62. pmcp-0.1.0/src/pmcp/summary/llm_summarizer.py +74 -0
  63. pmcp-0.1.0/src/pmcp/summary/template_fallback.py +105 -0
  64. pmcp-0.1.0/src/pmcp/tools/__init__.py +5 -0
  65. pmcp-0.1.0/src/pmcp/tools/handlers.py +1304 -0
  66. pmcp-0.1.0/src/pmcp/types.py +550 -0
  67. pmcp-0.1.0/tests/__init__.py +1 -0
  68. pmcp-0.1.0/tests/conftest.py +369 -0
  69. pmcp-0.1.0/tests/test_cli.py +660 -0
  70. pmcp-0.1.0/tests/test_client_manager.py +565 -0
  71. pmcp-0.1.0/tests/test_config_loader.py +134 -0
  72. pmcp-0.1.0/tests/test_errors.py +202 -0
  73. pmcp-0.1.0/tests/test_integration.py +236 -0
  74. pmcp-0.1.0/tests/test_manifest.py +998 -0
  75. pmcp-0.1.0/tests/test_policy.py +228 -0
  76. pmcp-0.1.0/tests/test_refresher.py +588 -0
  77. pmcp-0.1.0/tests/test_server.py +230 -0
  78. pmcp-0.1.0/tests/test_server_lifecycle.py +135 -0
  79. pmcp-0.1.0/tests/test_summary.py +159 -0
  80. pmcp-0.1.0/tests/test_tools.py +252 -0
  81. pmcp-0.1.0/tests/test_version_checker.py +446 -0
  82. pmcp-0.1.0/uv.lock +1935 -0
@@ -0,0 +1,677 @@
1
+ # Plan: Dynamic Capability Discovery & Provisioning
2
+
3
+ ## Goal
4
+ Add environment-aware capability resolution with on-demand MCP server provisioning, CLI detection, and LLM-powered request matching.
5
+
6
+ ## Features
7
+
8
+ ### 1. Environment Detection
9
+ - Probe inherited environment on startup (PATH, installed CLIs)
10
+ - Cache detected CLIs for fast lookup
11
+ - Optional `gateway.sync_environment` for explicit environment info
12
+
13
+ ### 2. Capability Request Tool
14
+ - New `gateway.request_capability` tool
15
+ - LLM-powered matching via BAML/Groq
16
+ - Priority: CLI → Active MCP → Dormant MCP → Not Available
17
+
18
+ ### 3. MCP Server Manifest
19
+ - YAML manifest of installable MCP servers
20
+ - Platform-specific install commands (Mac, WSL/Linux)
21
+ - Keyword matching + LLM semantic matching
22
+ - API key requirements with .env.example
23
+
24
+ ### 4. On-Demand Provisioning
25
+ - Install MCP server from manifest when requested
26
+ - Spin up and index tools
27
+ - Return new tools to Claude
28
+
29
+ ---
30
+
31
+ ## New Files
32
+
33
+ ### `src/mcp_gateway/manifest/`
34
+ ```
35
+ manifest/
36
+ ├── __init__.py
37
+ ├── loader.py # Load and parse manifest.yaml
38
+ ├── matcher.py # BAML-powered request matching
39
+ ├── installer.py # Platform-specific installation
40
+ ├── environment.py # CLI detection and env probing
41
+ └── manifest.yaml # Server definitions
42
+ ```
43
+
44
+ ### `baml_src/capability_match.baml`
45
+ ```baml
46
+ class ManifestEntry {
47
+ name string
48
+ keywords string[]
49
+ description string
50
+ }
51
+
52
+ class MatchResult {
53
+ matched bool
54
+ entry_name string @description("Name of matched manifest entry or empty")
55
+ confidence float @description("0.0 to 1.0 confidence score")
56
+ reasoning string @description("Why this matches or doesn't")
57
+ }
58
+
59
+ function MatchCapabilityRequest(
60
+ query: string,
61
+ manifest_entries: ManifestEntry[]
62
+ ) -> MatchResult {
63
+ client Groq
64
+ prompt #"
65
+ Match the user's capability request to the best manifest entry.
66
+
67
+ User request: {{ query }}
68
+
69
+ Available entries:
70
+ {% for entry in manifest_entries %}
71
+ - {{ entry.name }}: {{ entry.description }}
72
+ Keywords: {{ entry.keywords | join(", ") }}
73
+ {% endfor %}
74
+
75
+ If no entry matches well, set matched=false.
76
+ Confidence should reflect how well the request matches.
77
+
78
+ {{ ctx.output_format }}
79
+ "#
80
+ }
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Manifest Schema
86
+
87
+ ### `src/mcp_gateway/manifest/manifest.yaml`
88
+
89
+ ```yaml
90
+ version: "1.0"
91
+
92
+ # Environment detection
93
+ cli_alternatives:
94
+ git:
95
+ keywords: [git, version control, commits, branches, repository, clone]
96
+ check_command: ["git", "--version"]
97
+ help_command: ["git", "--help"]
98
+ description: "Git version control CLI"
99
+ prefer_mcp_for: [github issues, pull requests, github actions, github api]
100
+
101
+ docker:
102
+ keywords: [docker, container, image, dockerfile, compose]
103
+ check_command: ["docker", "--version"]
104
+ help_command: ["docker", "--help"]
105
+ description: "Docker container CLI"
106
+
107
+ kubectl:
108
+ keywords: [kubernetes, k8s, pods, deployments, services, helm]
109
+ check_command: ["kubectl", "version", "--client"]
110
+ help_command: ["kubectl", "--help"]
111
+ description: "Kubernetes CLI"
112
+
113
+ node:
114
+ keywords: [node, nodejs, npm, javascript, js]
115
+ check_command: ["node", "--version"]
116
+ help_command: ["node", "--help"]
117
+ description: "Node.js runtime"
118
+
119
+ python:
120
+ keywords: [python, pip, python3]
121
+ check_command: ["python3", "--version"]
122
+ help_command: ["python3", "--help"]
123
+ description: "Python interpreter"
124
+
125
+ aws:
126
+ keywords: [aws, amazon, s3, ec2, lambda, cloudformation]
127
+ check_command: ["aws", "--version"]
128
+ help_command: ["aws", "help"]
129
+ description: "AWS CLI"
130
+
131
+ gcloud:
132
+ keywords: [gcp, google cloud, gcloud, bigquery]
133
+ check_command: ["gcloud", "--version"]
134
+ help_command: ["gcloud", "--help"]
135
+ description: "Google Cloud CLI"
136
+
137
+ az:
138
+ keywords: [azure, az, microsoft cloud]
139
+ check_command: ["az", "--version"]
140
+ help_command: ["az", "--help"]
141
+ description: "Azure CLI"
142
+
143
+ # MCP Servers (dormant until requested)
144
+ servers:
145
+ # === No API Key Required ===
146
+
147
+ playwright:
148
+ description: "Browser automation - navigation, clicks, screenshots, DOM inspection"
149
+ keywords: [browser, web, automation, playwright, screenshot, click, navigate, scrape]
150
+ install:
151
+ mac: ["npx", "-y", "@anthropic/mcp-playwright"]
152
+ wsl: ["npx", "-y", "@anthropic/mcp-playwright"]
153
+ command: "npx"
154
+ args: ["-y", "@anthropic/mcp-playwright"]
155
+ requires_api_key: false
156
+ auto_start: true # Start by default
157
+
158
+ claude-in-chrome:
159
+ description: "Chrome browser control - read pages, click, type, navigate, screenshots"
160
+ keywords: [chrome, browser, web, automation, screenshot, click, navigate, tabs]
161
+ install:
162
+ mac: ["npx", "-y", "@anthropic/mcp-claude-in-chrome"]
163
+ wsl: ["npx", "-y", "@anthropic/mcp-claude-in-chrome"]
164
+ command: "npx"
165
+ args: ["-y", "@anthropic/mcp-claude-in-chrome"]
166
+ requires_api_key: false
167
+ auto_start: true # Start by default
168
+
169
+ # === Bright Data MCPs (Web Scraping & Data) ===
170
+
171
+ brightdata-scraper:
172
+ description: "Web scraping at scale - scrape any website with rotating proxies and CAPTCHA solving"
173
+ keywords: [scrape, scraping, web data, extract, crawl, brightdata, bright data, proxy]
174
+ install:
175
+ mac: ["npx", "-y", "@brightdata/mcp-scraper"]
176
+ wsl: ["npx", "-y", "@brightdata/mcp-scraper"]
177
+ command: "npx"
178
+ args: ["-y", "@brightdata/mcp-scraper"]
179
+ requires_api_key: true
180
+ env_var: "BRIGHTDATA_API_KEY"
181
+ env_instructions: "Get API key at https://brightdata.com/cp/api_tokens"
182
+ auto_start: true # Start by default
183
+
184
+ brightdata-serp:
185
+ description: "Search engine results - Google, Bing, DuckDuckGo SERP data"
186
+ keywords: [search, serp, google, bing, search results, seo, brightdata]
187
+ install:
188
+ mac: ["npx", "-y", "@brightdata/mcp-serp"]
189
+ wsl: ["npx", "-y", "@brightdata/mcp-serp"]
190
+ command: "npx"
191
+ args: ["-y", "@brightdata/mcp-serp"]
192
+ requires_api_key: true
193
+ env_var: "BRIGHTDATA_API_KEY"
194
+ env_instructions: "Get API key at https://brightdata.com/cp/api_tokens"
195
+ auto_start: true # Start by default
196
+
197
+ brightdata-unlocker:
198
+ description: "Web Unlocker - access any website bypassing blocks and CAPTCHAs"
199
+ keywords: [unblock, captcha, bypass, proxy, brightdata, anti-bot]
200
+ install:
201
+ mac: ["npx", "-y", "@brightdata/mcp-unlocker"]
202
+ wsl: ["npx", "-y", "@brightdata/mcp-unlocker"]
203
+ command: "npx"
204
+ args: ["-y", "@brightdata/mcp-unlocker"]
205
+ requires_api_key: true
206
+ env_var: "BRIGHTDATA_API_KEY"
207
+ env_instructions: "Get API key at https://brightdata.com/cp/api_tokens"
208
+
209
+ brightdata-datasets:
210
+ description: "Pre-built datasets - Amazon, LinkedIn, Google Maps, etc."
211
+ keywords: [dataset, data, amazon, linkedin, google maps, ecommerce, brightdata]
212
+ install:
213
+ mac: ["npx", "-y", "@brightdata/mcp-datasets"]
214
+ wsl: ["npx", "-y", "@brightdata/mcp-datasets"]
215
+ command: "npx"
216
+ args: ["-y", "@brightdata/mcp-datasets"]
217
+ requires_api_key: true
218
+ env_var: "BRIGHTDATA_API_KEY"
219
+ env_instructions: "Get API key at https://brightdata.com/cp/api_tokens"
220
+
221
+ filesystem:
222
+ description: "File system operations - read, write, search files"
223
+ keywords: [file, filesystem, read, write, directory, folder, fs]
224
+ install:
225
+ mac: ["npx", "-y", "@anthropic/mcp-filesystem"]
226
+ wsl: ["npx", "-y", "@anthropic/mcp-filesystem"]
227
+ command: "npx"
228
+ args: ["-y", "@anthropic/mcp-filesystem", "/"]
229
+ requires_api_key: false
230
+
231
+ memory:
232
+ description: "Persistent memory - store and retrieve information across sessions"
233
+ keywords: [memory, remember, store, persist, recall, notes]
234
+ install:
235
+ mac: ["npx", "-y", "@anthropic/mcp-memory"]
236
+ wsl: ["npx", "-y", "@anthropic/mcp-memory"]
237
+ command: "npx"
238
+ args: ["-y", "@anthropic/mcp-memory"]
239
+ requires_api_key: false
240
+
241
+ fetch:
242
+ description: "HTTP requests - fetch web pages, APIs, download content"
243
+ keywords: [http, fetch, request, api, web, download, curl]
244
+ install:
245
+ mac: ["npx", "-y", "@anthropic/mcp-fetch"]
246
+ wsl: ["npx", "-y", "@anthropic/mcp-fetch"]
247
+ command: "npx"
248
+ args: ["-y", "@anthropic/mcp-fetch"]
249
+ requires_api_key: false
250
+
251
+ sqlite:
252
+ description: "SQLite database operations"
253
+ keywords: [sqlite, database, sql, query, db]
254
+ install:
255
+ mac: ["npx", "-y", "@anthropic/mcp-sqlite"]
256
+ wsl: ["npx", "-y", "@anthropic/mcp-sqlite"]
257
+ command: "npx"
258
+ args: ["-y", "@anthropic/mcp-sqlite"]
259
+ requires_api_key: false
260
+
261
+ puppeteer:
262
+ description: "Headless Chrome automation"
263
+ keywords: [puppeteer, chrome, headless, browser, scraping]
264
+ install:
265
+ mac: ["npx", "-y", "@anthropic/mcp-puppeteer"]
266
+ wsl: ["npx", "-y", "@anthropic/mcp-puppeteer"]
267
+ command: "npx"
268
+ args: ["-y", "@anthropic/mcp-puppeteer"]
269
+ requires_api_key: false
270
+
271
+ # === API Key Required ===
272
+
273
+ github:
274
+ description: "GitHub API - issues, PRs, repos, actions, code search"
275
+ keywords: [github, issues, pull request, pr, repository, actions, workflows, code review]
276
+ install:
277
+ mac: ["npx", "-y", "@anthropic/mcp-github"]
278
+ wsl: ["npx", "-y", "@anthropic/mcp-github"]
279
+ command: "npx"
280
+ args: ["-y", "@anthropic/mcp-github"]
281
+ requires_api_key: true
282
+ env_var: "GITHUB_TOKEN"
283
+ env_instructions: "Create at https://github.com/settings/tokens with repo scope"
284
+
285
+ slack:
286
+ description: "Slack messaging - channels, DMs, search, notifications"
287
+ keywords: [slack, messaging, chat, channels, dm, notifications, team]
288
+ install:
289
+ mac: ["npx", "-y", "@anthropic/mcp-slack"]
290
+ wsl: ["npx", "-y", "@anthropic/mcp-slack"]
291
+ command: "npx"
292
+ args: ["-y", "@anthropic/mcp-slack"]
293
+ requires_api_key: true
294
+ env_var: "SLACK_TOKEN"
295
+ env_instructions: "Create Slack app at https://api.slack.com/apps and get Bot Token"
296
+
297
+ linear:
298
+ description: "Linear issue tracking - issues, projects, cycles"
299
+ keywords: [linear, issues, project management, tickets, bugs, tasks]
300
+ install:
301
+ mac: ["npx", "-y", "@anthropic/mcp-linear"]
302
+ wsl: ["npx", "-y", "@anthropic/mcp-linear"]
303
+ command: "npx"
304
+ args: ["-y", "@anthropic/mcp-linear"]
305
+ requires_api_key: true
306
+ env_var: "LINEAR_API_KEY"
307
+ env_instructions: "Create at https://linear.app/settings/api"
308
+
309
+ notion:
310
+ description: "Notion workspace - pages, databases, search"
311
+ keywords: [notion, wiki, documentation, pages, databases, notes]
312
+ install:
313
+ mac: ["npx", "-y", "@anthropic/mcp-notion"]
314
+ wsl: ["npx", "-y", "@anthropic/mcp-notion"]
315
+ command: "npx"
316
+ args: ["-y", "@anthropic/mcp-notion"]
317
+ requires_api_key: true
318
+ env_var: "NOTION_TOKEN"
319
+ env_instructions: "Create integration at https://www.notion.so/my-integrations"
320
+
321
+ postgres:
322
+ description: "PostgreSQL database operations"
323
+ keywords: [postgres, postgresql, database, sql, query]
324
+ install:
325
+ mac: ["npx", "-y", "@anthropic/mcp-postgres"]
326
+ wsl: ["npx", "-y", "@anthropic/mcp-postgres"]
327
+ command: "npx"
328
+ args: ["-y", "@anthropic/mcp-postgres"]
329
+ requires_api_key: true
330
+ env_var: "POSTGRES_URL"
331
+ env_instructions: "PostgreSQL connection string: postgresql://user:pass@host:5432/db"
332
+
333
+ sentry:
334
+ description: "Sentry error tracking - issues, events, releases"
335
+ keywords: [sentry, errors, monitoring, debugging, exceptions, crashes]
336
+ install:
337
+ mac: ["npx", "-y", "@anthropic/mcp-sentry"]
338
+ wsl: ["npx", "-y", "@anthropic/mcp-sentry"]
339
+ command: "npx"
340
+ args: ["-y", "@anthropic/mcp-sentry"]
341
+ requires_api_key: true
342
+ env_var: "SENTRY_AUTH_TOKEN"
343
+ env_instructions: "Create at https://sentry.io/settings/account/api/auth-tokens/"
344
+
345
+ google-drive:
346
+ description: "Google Drive - files, folders, search, sharing"
347
+ keywords: [google drive, gdrive, docs, sheets, files, cloud storage]
348
+ install:
349
+ mac: ["npx", "-y", "@anthropic/mcp-google-drive"]
350
+ wsl: ["npx", "-y", "@anthropic/mcp-google-drive"]
351
+ command: "npx"
352
+ args: ["-y", "@anthropic/mcp-google-drive"]
353
+ requires_api_key: true
354
+ env_var: "GOOGLE_CREDENTIALS"
355
+ env_instructions: "Create OAuth credentials at https://console.cloud.google.com/apis/credentials"
356
+
357
+ context7:
358
+ description: "Library documentation lookup - up-to-date docs for any package"
359
+ keywords: [documentation, docs, library, package, api reference, context7]
360
+ install:
361
+ mac: ["npx", "-y", "@upstash/context7-mcp"]
362
+ wsl: ["npx", "-y", "@upstash/context7-mcp"]
363
+ command: "npx"
364
+ args: ["-y", "@upstash/context7-mcp"]
365
+ requires_api_key: false
366
+ auto_start: true
367
+
368
+ # Discovery queue for unmatched requests
369
+ discovery_queue_path: ".mcp-gateway/discovery_queue.json"
370
+ ```
371
+
372
+ ---
373
+
374
+ ## .env.example
375
+
376
+ ```bash
377
+ # MCP Gateway Environment Variables
378
+ # Copy to .env and fill in your API keys
379
+
380
+ # === Required for LLM-powered matching ===
381
+ GROQ_API_KEY=gsk_your_groq_api_key_here
382
+
383
+ # === Default Auto-Start Servers (API keys required) ===
384
+
385
+ # Bright Data - https://brightdata.com/cp/api_tokens
386
+ # Required for: brightdata-scraper, brightdata-serp, brightdata-unlocker, brightdata-datasets
387
+ BRIGHTDATA_API_KEY=your_brightdata_api_key_here
388
+
389
+ # === Optional MCP Server API Keys ===
390
+ # Uncomment and fill in as needed
391
+
392
+ # GitHub - https://github.com/settings/tokens (repo scope)
393
+ #GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
394
+
395
+ # Slack - https://api.slack.com/apps (Bot Token)
396
+ #SLACK_TOKEN=xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx
397
+
398
+ # Linear - https://linear.app/settings/api
399
+ #LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
400
+
401
+ # Notion - https://www.notion.so/my-integrations
402
+ #NOTION_TOKEN=secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
403
+
404
+ # PostgreSQL - Connection string
405
+ #POSTGRES_URL=postgresql://user:password@localhost:5432/database
406
+
407
+ # Sentry - https://sentry.io/settings/account/api/auth-tokens/
408
+ #SENTRY_AUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
409
+
410
+ # Google Drive - OAuth credentials JSON path
411
+ #GOOGLE_CREDENTIALS=/path/to/credentials.json
412
+ ```
413
+
414
+ ---
415
+
416
+ ## New Types
417
+
418
+ ### `src/mcp_gateway/types.py` additions
419
+
420
+ ```python
421
+ class CapabilityRequestInput(BaseModel):
422
+ """Input for gateway.request_capability."""
423
+ query: str = Field(min_length=1, description="Natural language capability request")
424
+ available_clis: list[str] | None = Field(default=None, description="CLIs Claude knows are available")
425
+
426
+ class CapabilityResolution(BaseModel):
427
+ """Result of capability resolution."""
428
+ status: Literal["use_cli", "available", "provisioned", "needs_api_key", "not_available"]
429
+
430
+ # For use_cli
431
+ cli: str | None = None
432
+ cli_path: str | None = None
433
+ cli_help: str | None = None
434
+ cli_examples: list[str] | None = None
435
+
436
+ # For available/provisioned
437
+ server: str | None = None
438
+ new_tools: list[CapabilityCard] | None = None
439
+
440
+ # For needs_api_key
441
+ env_var: str | None = None
442
+ env_path: str | None = None
443
+ env_instructions: str | None = None
444
+
445
+ # For not_available
446
+ logged_for_discovery: bool = False
447
+
448
+ message: str
449
+
450
+ class EnvironmentInfo(BaseModel):
451
+ """Environment information from Claude or detected."""
452
+ path: str | None = None
453
+ cwd: str | None = None
454
+ platform: Literal["mac", "wsl", "linux", "windows"] | None = None
455
+ detected_clis: list[str] = Field(default_factory=list)
456
+ ```
457
+
458
+ ---
459
+
460
+ ## New Gateway Tools
461
+
462
+ ### `gateway.request_capability`
463
+ ```python
464
+ Tool(
465
+ name="gateway.request_capability",
466
+ description=(
467
+ "Request a capability that may not be currently available. "
468
+ "The gateway will check installed CLIs, active servers, and dormant servers. "
469
+ "May install and start a new MCP server if available in manifest."
470
+ ),
471
+ inputSchema={
472
+ "type": "object",
473
+ "properties": {
474
+ "query": {
475
+ "type": "string",
476
+ "description": "Natural language description of needed capability"
477
+ },
478
+ "available_clis": {
479
+ "type": "array",
480
+ "items": {"type": "string"},
481
+ "description": "Optional: CLIs you know are available in the environment"
482
+ }
483
+ },
484
+ "required": ["query"]
485
+ }
486
+ )
487
+ ```
488
+
489
+ ### `gateway.sync_environment`
490
+ ```python
491
+ Tool(
492
+ name="gateway.sync_environment",
493
+ description=(
494
+ "Sync environment information with the gateway. "
495
+ "Call this if the gateway's environment detection seems incorrect."
496
+ ),
497
+ inputSchema={
498
+ "type": "object",
499
+ "properties": {
500
+ "platform": {
501
+ "type": "string",
502
+ "enum": ["mac", "wsl", "linux", "windows"]
503
+ },
504
+ "detected_clis": {
505
+ "type": "array",
506
+ "items": {"type": "string"},
507
+ "description": "CLIs confirmed to be installed"
508
+ }
509
+ }
510
+ }
511
+ )
512
+ ```
513
+
514
+ ---
515
+
516
+ ## Implementation Flow
517
+
518
+ ### Startup
519
+ ```python
520
+ async def initialize(self):
521
+ # 1. Detect platform
522
+ self._platform = detect_platform() # mac, wsl, linux
523
+
524
+ # 2. Load manifest
525
+ self._manifest = load_manifest()
526
+
527
+ # 3. Probe environment for CLIs
528
+ self._detected_clis = await probe_clis(self._manifest.cli_alternatives)
529
+
530
+ # 4. Connect to auto_start servers (playwright, context7)
531
+ await self._connect_auto_start_servers()
532
+
533
+ # 5. Generate capability summary (includes detected CLIs)
534
+ self._capability_summary = await generate_capability_summary(
535
+ tools=self._client_manager.get_all_tools(),
536
+ detected_clis=self._detected_clis,
537
+ )
538
+ ```
539
+
540
+ ### request_capability Flow
541
+ ```python
542
+ async def request_capability(self, input_data: dict) -> CapabilityResolution:
543
+ parsed = CapabilityRequestInput.model_validate(input_data)
544
+ query = parsed.query
545
+
546
+ # 1. Check if active MCP server already has matching tools
547
+ if tools := self._search_active_tools(query):
548
+ return CapabilityResolution(
549
+ status="available",
550
+ message=f"Tools already available",
551
+ new_tools=tools
552
+ )
553
+
554
+ # 2. Check for matching CLI
555
+ if cli := self._match_cli(query):
556
+ if cli.name in self._detected_clis:
557
+ help_output = await self._get_cli_help(cli)
558
+ return CapabilityResolution(
559
+ status="use_cli",
560
+ cli=cli.name,
561
+ cli_path=self._cli_paths.get(cli.name),
562
+ cli_help=help_output,
563
+ cli_examples=cli.examples,
564
+ message=f"Use {cli.name} CLI via Bash tool"
565
+ )
566
+
567
+ # 3. Use BAML/Groq to match against manifest
568
+ match = await self._match_manifest(query)
569
+
570
+ if not match.matched:
571
+ # Log for discovery
572
+ await self._log_discovery_request(query)
573
+ return CapabilityResolution(
574
+ status="not_available",
575
+ logged_for_discovery=True,
576
+ message="No matching capability found. Request logged for future discovery."
577
+ )
578
+
579
+ server_config = self._manifest.servers[match.entry_name]
580
+
581
+ # 4. Check API key if required
582
+ if server_config.requires_api_key:
583
+ env_var = server_config.env_var
584
+ if not os.environ.get(env_var):
585
+ return CapabilityResolution(
586
+ status="needs_api_key",
587
+ server=match.entry_name,
588
+ env_var=env_var,
589
+ env_path=str(Path.cwd() / ".env"),
590
+ env_instructions=server_config.env_instructions,
591
+ message=f"Capability available but requires {env_var} to be set"
592
+ )
593
+
594
+ # 5. Install and start server
595
+ try:
596
+ await self._install_server(match.entry_name)
597
+ await self._start_server(match.entry_name)
598
+
599
+ new_tools = self._client_manager.get_tools_for_server(match.entry_name)
600
+
601
+ return CapabilityResolution(
602
+ status="provisioned",
603
+ server=match.entry_name,
604
+ new_tools=[self._to_capability_card(t) for t in new_tools],
605
+ message=f"Started {match.entry_name} MCP server with {len(new_tools)} tools"
606
+ )
607
+ except Exception as e:
608
+ return CapabilityResolution(
609
+ status="not_available",
610
+ message=f"Failed to provision {match.entry_name}: {e}"
611
+ )
612
+ ```
613
+
614
+ ---
615
+
616
+ ## Files to Create/Modify
617
+
618
+ | File | Action |
619
+ |------|--------|
620
+ | `src/mcp_gateway/manifest/__init__.py` | Create |
621
+ | `src/mcp_gateway/manifest/loader.py` | Create |
622
+ | `src/mcp_gateway/manifest/matcher.py` | Create |
623
+ | `src/mcp_gateway/manifest/installer.py` | Create |
624
+ | `src/mcp_gateway/manifest/environment.py` | Create |
625
+ | `src/mcp_gateway/manifest/manifest.yaml` | Create |
626
+ | `baml_src/capability_match.baml` | Create |
627
+ | `.env.example` | Create |
628
+ | `src/mcp_gateway/types.py` | Add new types |
629
+ | `src/mcp_gateway/tools/handlers.py` | Add new tools |
630
+ | `src/mcp_gateway/server.py` | Add manifest loading, env detection |
631
+ | `tests/test_manifest.py` | Create |
632
+ | `tests/test_capability_request.py` | Create |
633
+
634
+ ---
635
+
636
+ ## Updated Capability Summary
637
+
638
+ The L1 handshake summary will now include detected CLIs:
639
+
640
+ ```
641
+ MCP Gateway capabilities:
642
+ • Browser Automation (playwright): Navigate, click, screenshot
643
+ • Documentation (context7): Library docs lookup
644
+
645
+ Detected CLIs (use via Bash): git, docker, node, python, aws
646
+
647
+ Use gateway.request_capability to discover more capabilities.
648
+ Use gateway.catalog_search to explore available tools.
649
+ ```
650
+
651
+ ---
652
+
653
+ ## Testing Plan
654
+
655
+ 1. **Unit tests**: CLI detection, manifest loading, BAML matching
656
+ 2. **Integration tests**:
657
+ - Request capability → CLI resolution
658
+ - Request capability → Server provisioning
659
+ - Request capability → API key needed response
660
+ 3. **E2E test**: Full flow with Groq API
661
+
662
+ ---
663
+
664
+ ## Execution Order
665
+
666
+ 1. Create manifest module structure
667
+ 2. Implement environment.py (CLI detection)
668
+ 3. Implement loader.py (manifest parsing)
669
+ 4. Add BAML capability_match.baml + regenerate client
670
+ 5. Implement matcher.py (BAML-powered matching)
671
+ 6. Implement installer.py (platform-specific install)
672
+ 7. Add new types to types.py
673
+ 8. Add new tools to handlers.py
674
+ 9. Update server.py initialization
675
+ 10. Create .env.example
676
+ 11. Add tests
677
+ 12. Run full test suite