codeply-cli 0.3.1 → 0.4.0
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.
- package/THIRD_PARTY_NOTICES.md +63 -63
- package/bin/codeply.js +127 -0
- package/lib/agent.mjs +2139 -2139
- package/lib/ai.mjs +6 -6
- package/lib/apply-patch.js +217 -217
- package/lib/arity-table.json +138 -138
- package/lib/arity.js +70 -70
- package/lib/auth.mjs +6 -6
- package/lib/cloud.mjs +708 -0
- package/lib/commands.js +103 -103
- package/lib/design-library/assemble-artifact.mjs +21 -21
- package/lib/design-library/cleanup.mjs +45 -45
- package/lib/design-library/index.json +22379 -22379
- package/lib/design-library/make-table-data.mjs +39 -39
- package/lib/diagnostics.js +192 -192
- package/lib/edit-engine.js +364 -364
- package/lib/github-agent.mjs +311 -307
- package/lib/history.js +75 -75
- package/lib/mcp.js +258 -258
- package/lib/native-tools.mjs +189 -189
- package/lib/oauth-connectors.js +2 -1
- package/lib/permissions.js +109 -109
- package/lib/plugins.js +353 -353
- package/lib/server.mjs +457 -457
- package/lib/session-db.js +220 -220
- package/lib/share.js +168 -168
- package/lib/skills.js +416 -416
- package/lib/snapshot.js +146 -146
- package/lib/terminal-hints.js +154 -154
- package/lib/tools.mjs +2012 -2012
- package/lib/typecheck-worker.js +147 -147
- package/lib/web-tools.js +158 -158
- package/package.json +49 -47
- package/skills/SOURCE.md +58 -58
- package/skills/accessibility/SKILL.md +147 -147
- package/skills/agent-architecture-audit/SKILL.md +257 -257
- package/skills/agent-eval/SKILL.md +147 -147
- package/skills/agent-harness-construction/SKILL.md +74 -74
- package/skills/agent-introspection-debugging/SKILL.md +154 -154
- package/skills/agent-payment-x402/SKILL.md +225 -225
- package/skills/agent-self-evaluation/SKILL.md +182 -182
- package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -71
- package/skills/agent-self-evaluation/references/hook-integration.md +64 -64
- package/skills/agent-sort/SKILL.md +216 -216
- package/skills/agentic-engineering/SKILL.md +64 -64
- package/skills/agentic-os/SKILL.md +388 -388
- package/skills/ai-first-engineering/SKILL.md +52 -52
- package/skills/ai-regression-testing/SKILL.md +386 -386
- package/skills/android-clean-architecture/SKILL.md +340 -340
- package/skills/angular-developer/SKILL.md +155 -155
- package/skills/angular-developer/references/angular-animations.md +160 -160
- package/skills/angular-developer/references/angular-aria.md +410 -410
- package/skills/angular-developer/references/cli.md +86 -86
- package/skills/angular-developer/references/component-harnesses.md +59 -59
- package/skills/angular-developer/references/component-styling.md +91 -91
- package/skills/angular-developer/references/components.md +117 -117
- package/skills/angular-developer/references/creating-services.md +97 -97
- package/skills/angular-developer/references/data-resolvers.md +69 -69
- package/skills/angular-developer/references/define-routes.md +67 -67
- package/skills/angular-developer/references/defining-providers.md +72 -72
- package/skills/angular-developer/references/di-fundamentals.md +120 -120
- package/skills/angular-developer/references/e2e-testing.md +56 -56
- package/skills/angular-developer/references/effects.md +83 -83
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -43
- package/skills/angular-developer/references/host-elements.md +80 -80
- package/skills/angular-developer/references/injection-context.md +63 -63
- package/skills/angular-developer/references/inputs.md +101 -101
- package/skills/angular-developer/references/linked-signal.md +59 -59
- package/skills/angular-developer/references/loading-strategies.md +61 -61
- package/skills/angular-developer/references/mcp.md +108 -108
- package/skills/angular-developer/references/navigate-to-routes.md +69 -69
- package/skills/angular-developer/references/outputs.md +86 -86
- package/skills/angular-developer/references/reactive-forms.md +122 -122
- package/skills/angular-developer/references/rendering-strategies.md +44 -44
- package/skills/angular-developer/references/resource.md +77 -77
- package/skills/angular-developer/references/route-animations.md +56 -56
- package/skills/angular-developer/references/route-guards.md +52 -52
- package/skills/angular-developer/references/router-lifecycle.md +45 -45
- package/skills/angular-developer/references/router-testing.md +87 -87
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -68
- package/skills/angular-developer/references/signal-forms.md +795 -795
- package/skills/angular-developer/references/signals-overview.md +94 -94
- package/skills/angular-developer/references/tailwind-css.md +69 -69
- package/skills/angular-developer/references/template-driven-forms.md +114 -114
- package/skills/angular-developer/references/testing-fundamentals.md +65 -65
- package/skills/api-connector-builder/SKILL.md +121 -121
- package/skills/api-design/SKILL.md +524 -524
- package/skills/architecture-decision-records/SKILL.md +180 -180
- package/skills/article-writing/SKILL.md +80 -80
- package/skills/automation-audit-ops/SKILL.md +143 -143
- package/skills/autonomous-agent-harness/SKILL.md +274 -274
- package/skills/autonomous-loops/SKILL.md +611 -611
- package/skills/backend-patterns/SKILL.md +562 -562
- package/skills/benchmark/SKILL.md +95 -95
- package/skills/benchmark-methodology/SKILL.md +191 -191
- package/skills/benchmark-optimization-loop/SKILL.md +71 -71
- package/skills/blender-motion-state-inspection/SKILL.md +165 -165
- package/skills/blueprint/SKILL.md +106 -106
- package/skills/brand-discovery/SKILL.md +145 -145
- package/skills/brand-discovery/references/10_purpose-why.md +40 -40
- package/skills/brand-discovery/references/20_positioning.md +44 -44
- package/skills/brand-discovery/references/30_audience-niche.md +52 -52
- package/skills/brand-discovery/references/40_personality-archetype.md +57 -57
- package/skills/brand-discovery/references/50_voice-tone.md +59 -59
- package/skills/brand-discovery/references/60_narrative-story.md +50 -50
- package/skills/brand-discovery/references/70_founder-tension.md +49 -49
- package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -133
- package/skills/brand-voice/SKILL.md +98 -98
- package/skills/brand-voice/references/voice-profile-schema.md +55 -55
- package/skills/browser-qa/SKILL.md +105 -105
- package/skills/bun-runtime/SKILL.md +85 -85
- package/skills/canary-watch/SKILL.md +108 -108
- package/skills/carrier-relationship-management/SKILL.md +212 -212
- package/skills/cisco-ios-patterns/SKILL.md +164 -164
- package/skills/ck/SKILL.md +148 -148
- package/skills/claude-devfleet/SKILL.md +112 -112
- package/skills/click-path-audit/SKILL.md +245 -245
- package/skills/clickhouse-io/SKILL.md +445 -445
- package/skills/code-tour/SKILL.md +254 -254
- package/skills/codebase-onboarding/SKILL.md +234 -234
- package/skills/codehealth-mcp/SKILL.md +167 -167
- package/skills/coding-standards/SKILL.md +551 -551
- package/skills/competitive-platform-analysis/SKILL.md +214 -214
- package/skills/competitive-report-structure/SKILL.md +162 -162
- package/skills/compose-multiplatform-patterns/SKILL.md +300 -300
- package/skills/config-gc/SKILL.md +120 -120
- package/skills/configure-ecc/SKILL.md +385 -385
- package/skills/connections-optimizer/SKILL.md +190 -190
- package/skills/content-engine/SKILL.md +132 -132
- package/skills/content-hash-cache-pattern/SKILL.md +162 -162
- package/skills/context-budget/SKILL.md +136 -136
- package/skills/continuous-agent-loop/SKILL.md +46 -46
- package/skills/continuous-learning/SKILL.md +132 -132
- package/skills/continuous-learning-v2/SKILL.md +361 -361
- package/skills/contract-first/SKILL.md +287 -287
- package/skills/cost-aware-llm-pipeline/SKILL.md +184 -184
- package/skills/cost-tracking/SKILL.md +97 -97
- package/skills/council/SKILL.md +204 -204
- package/skills/cpp-coding-standards/SKILL.md +724 -724
- package/skills/cpp-testing/SKILL.md +325 -325
- package/skills/crosspost/SKILL.md +112 -112
- package/skills/csharp-testing/SKILL.md +322 -322
- package/skills/customer-billing-ops/SKILL.md +141 -141
- package/skills/customs-trade-compliance/SKILL.md +263 -263
- package/skills/dart-flutter-patterns/SKILL.md +564 -564
- package/skills/dashboard-builder/SKILL.md +109 -109
- package/skills/data-scraper-agent/SKILL.md +765 -765
- package/skills/data-throughput-accelerator/SKILL.md +74 -74
- package/skills/database-migrations/SKILL.md +430 -430
- package/skills/deep-research/SKILL.md +160 -160
- package/skills/defi-amm-security/SKILL.md +167 -167
- package/skills/delivery-gate/SKILL.md +126 -126
- package/skills/deployment-patterns/SKILL.md +428 -428
- package/skills/design-system/SKILL.md +83 -83
- package/skills/django-celery/SKILL.md +458 -458
- package/skills/django-patterns/SKILL.md +735 -735
- package/skills/django-security/SKILL.md +644 -644
- package/skills/django-tdd/SKILL.md +730 -730
- package/skills/django-verification/SKILL.md +470 -470
- package/skills/dmux-workflows/SKILL.md +192 -192
- package/skills/docker-patterns/SKILL.md +365 -365
- package/skills/documentation-lookup/SKILL.md +91 -91
- package/skills/dotnet-patterns/SKILL.md +322 -322
- package/skills/dynamic-workflow-mode/SKILL.md +124 -124
- package/skills/e2e-testing/SKILL.md +327 -327
- package/skills/ecc-guide/SKILL.md +190 -190
- package/skills/ecc-recipes/SKILL.md +149 -149
- package/skills/ecc-tools-cost-audit/SKILL.md +161 -161
- package/skills/email-ops/SKILL.md +122 -122
- package/skills/energy-procurement/SKILL.md +228 -228
- package/skills/enterprise-agent-ops/SKILL.md +51 -51
- package/skills/error-handling/SKILL.md +377 -377
- package/skills/eval-harness/SKILL.md +271 -271
- package/skills/evm-token-decimals/SKILL.md +131 -131
- package/skills/exa-search/SKILL.md +108 -108
- package/skills/fal-ai-media/SKILL.md +289 -289
- package/skills/fastapi-patterns/SKILL.md +514 -514
- package/skills/finance-billing-ops/SKILL.md +128 -128
- package/skills/flox-environments/SKILL.md +497 -497
- package/skills/flutter-dart-code-review/SKILL.md +436 -436
- package/skills/foundation-models-on-device/SKILL.md +243 -243
- package/skills/frontend-a11y/SKILL.md +446 -446
- package/skills/frontend-design-direction/SKILL.md +93 -93
- package/skills/frontend-patterns/SKILL.md +657 -657
- package/skills/frontend-slides/SKILL.md +185 -185
- package/skills/fsharp-testing/SKILL.md +281 -281
- package/skills/gan-style-harness/SKILL.md +279 -279
- package/skills/gateguard/SKILL.md +133 -133
- package/skills/generating-python-installer/SKILL.md +820 -820
- package/skills/git-workflow/SKILL.md +716 -716
- package/skills/github-ops/SKILL.md +145 -145
- package/skills/golang-patterns/SKILL.md +676 -676
- package/skills/golang-testing/SKILL.md +721 -721
- package/skills/google-workspace-ops/SKILL.md +96 -96
- package/skills/growth-log/SKILL.md +128 -128
- package/skills/healthcare-cdss-patterns/SKILL.md +246 -246
- package/skills/healthcare-emr-patterns/SKILL.md +160 -160
- package/skills/healthcare-eval-harness/SKILL.md +208 -208
- package/skills/healthcare-phi-compliance/SKILL.md +146 -146
- package/skills/hermes-imports/SKILL.md +89 -89
- package/skills/hexagonal-architecture/SKILL.md +277 -277
- package/skills/hipaa-compliance/SKILL.md +79 -79
- package/skills/homelab-network-readiness/SKILL.md +170 -170
- package/skills/homelab-network-setup/SKILL.md +130 -130
- package/skills/homelab-pihole-dns/SKILL.md +275 -275
- package/skills/homelab-vlan-segmentation/SKILL.md +312 -312
- package/skills/homelab-wireguard-vpn/SKILL.md +306 -306
- package/skills/hookify-rules/SKILL.md +128 -128
- package/skills/inherit-legacy-style/SKILL.md +157 -157
- package/skills/intent-driven-development/SKILL.md +360 -360
- package/skills/inventory-demand-planning/SKILL.md +247 -247
- package/skills/investor-materials/SKILL.md +97 -97
- package/skills/investor-outreach/SKILL.md +92 -92
- package/skills/ios-icon-gen/SKILL.md +158 -158
- package/skills/iterative-retrieval/SKILL.md +212 -212
- package/skills/ito-basket-compare/SKILL.md +64 -64
- package/skills/ito-compute/SKILL.md +129 -129
- package/skills/ito-data-atlas-agent/SKILL.md +64 -64
- package/skills/ito-market-intelligence/SKILL.md +61 -61
- package/skills/ito-trade-planner/SKILL.md +68 -68
- package/skills/java-coding-standards/SKILL.md +384 -384
- package/skills/jira-integration/SKILL.md +303 -303
- package/skills/jpa-patterns/SKILL.md +152 -152
- package/skills/knowledge-ops/SKILL.md +155 -155
- package/skills/kotlin-coroutines-flows/SKILL.md +285 -285
- package/skills/kotlin-exposed-patterns/SKILL.md +720 -720
- package/skills/kotlin-ktor-patterns/SKILL.md +690 -690
- package/skills/kotlin-patterns/SKILL.md +712 -712
- package/skills/kotlin-testing/SKILL.md +825 -825
- package/skills/kubernetes-patterns/SKILL.md +756 -756
- package/skills/laravel-patterns/SKILL.md +416 -416
- package/skills/laravel-plugin-discovery/SKILL.md +230 -230
- package/skills/laravel-security/SKILL.md +948 -948
- package/skills/laravel-tdd/SKILL.md +675 -675
- package/skills/laravel-verification/SKILL.md +180 -180
- package/skills/latency-critical-systems/SKILL.md +75 -75
- package/skills/lead-intelligence/SKILL.md +322 -322
- package/skills/liquid-glass-design/SKILL.md +279 -279
- package/skills/llm-trading-agent-security/SKILL.md +147 -147
- package/skills/logistics-exception-management/SKILL.md +222 -222
- package/skills/loop-design-check/SKILL.md +143 -143
- package/skills/mailtrap-email-integration/SKILL.md +77 -77
- package/skills/make-interfaces-feel-better/SKILL.md +152 -152
- package/skills/manim-video/SKILL.md +90 -90
- package/skills/market-research/SKILL.md +76 -76
- package/skills/marketing-campaign/SKILL.md +114 -114
- package/skills/mcp-server-patterns/SKILL.md +70 -70
- package/skills/messages-ops/SKILL.md +105 -105
- package/skills/ml-adoption-playbook/SKILL.md +57 -57
- package/skills/mle-workflow/SKILL.md +348 -348
- package/skills/motion-advanced/SKILL.md +596 -596
- package/skills/motion-foundations/SKILL.md +299 -299
- package/skills/motion-patterns/SKILL.md +434 -434
- package/skills/motion-ui/SKILL.md +576 -576
- package/skills/mysql-patterns/SKILL.md +413 -413
- package/skills/nanoclaw-repl/SKILL.md +34 -34
- package/skills/nestjs-patterns/SKILL.md +231 -231
- package/skills/netmiko-ssh-automation/SKILL.md +174 -174
- package/skills/network-bgp-diagnostics/SKILL.md +168 -168
- package/skills/network-config-validation/SKILL.md +211 -211
- package/skills/network-interface-health/SKILL.md +153 -153
- package/skills/nextjs-turbopack/SKILL.md +58 -58
- package/skills/nodejs-keccak256/SKILL.md +103 -103
- package/skills/nutrient-document-processing/SKILL.md +168 -168
- package/skills/nuxt4-patterns/SKILL.md +101 -101
- package/skills/openclaw-persona-forge/SKILL.md +289 -289
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -124
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -53
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -53
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -48
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -39
- package/skills/openclaw-persona-forge/references/output-template.md +166 -166
- package/skills/opensource-pipeline/SKILL.md +256 -256
- package/skills/orch-add-feature/SKILL.md +45 -45
- package/skills/orch-build-mvp/SKILL.md +49 -49
- package/skills/orch-change-feature/SKILL.md +43 -43
- package/skills/orch-fix-defect/SKILL.md +43 -43
- package/skills/orch-pipeline/SKILL.md +121 -121
- package/skills/orch-refine-code/SKILL.md +44 -44
- package/skills/parallel-execution-optimizer/SKILL.md +74 -74
- package/skills/perl-patterns/SKILL.md +505 -505
- package/skills/perl-security/SKILL.md +504 -504
- package/skills/perl-testing/SKILL.md +476 -476
- package/skills/plan-canvas/SKILL.md +153 -153
- package/skills/plan-orchestrate/SKILL.md +263 -263
- package/skills/plankton-code-quality/SKILL.md +237 -237
- package/skills/postgres-patterns/SKILL.md +148 -148
- package/skills/prediction-market-oracle-research/SKILL.md +64 -64
- package/skills/prediction-market-risk-review/SKILL.md +61 -61
- package/skills/premium-web-design/SKILL.md +154 -154
- package/skills/prisma-patterns/SKILL.md +401 -401
- package/skills/product-capability/SKILL.md +142 -142
- package/skills/product-lens/SKILL.md +93 -93
- package/skills/production-audit/SKILL.md +207 -207
- package/skills/production-scheduling/SKILL.md +238 -238
- package/skills/project-flow-ops/SKILL.md +112 -112
- package/skills/prompt-optimizer/SKILL.md +398 -398
- package/skills/python-patterns/SKILL.md +751 -751
- package/skills/python-testing/SKILL.md +817 -817
- package/skills/pytorch-patterns/SKILL.md +397 -397
- package/skills/quality-nonconformance/SKILL.md +260 -260
- package/skills/quarkus-patterns/SKILL.md +723 -723
- package/skills/quarkus-security/SKILL.md +468 -468
- package/skills/quarkus-tdd/SKILL.md +812 -812
- package/skills/quarkus-verification/SKILL.md +480 -480
- package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -68
- package/skills/react-native-patterns/SKILL.md +326 -326
- package/skills/react-patterns/SKILL.md +342 -342
- package/skills/react-performance/SKILL.md +575 -575
- package/skills/react-testing/SKILL.md +424 -424
- package/skills/recsys-pipeline-architect/SKILL.md +115 -115
- package/skills/recursive-decision-ledger/SKILL.md +81 -81
- package/skills/redis-patterns/SKILL.md +404 -404
- package/skills/regex-vs-llm-structured-text/SKILL.md +221 -221
- package/skills/remotion-video-creation/SKILL.md +43 -43
- package/skills/repo-scan/SKILL.md +79 -79
- package/skills/research-ops/SKILL.md +113 -113
- package/skills/returns-reverse-logistics/SKILL.md +240 -240
- package/skills/rules-distill/SKILL.md +265 -265
- package/skills/rust-patterns/SKILL.md +500 -500
- package/skills/rust-testing/SKILL.md +501 -501
- package/skills/safety-guard/SKILL.md +76 -76
- package/skills/santa-method/SKILL.md +307 -307
- package/skills/scientific-db-pubmed-database/SKILL.md +176 -176
- package/skills/scientific-db-uspto-database/SKILL.md +178 -178
- package/skills/scientific-pkg-gget/SKILL.md +167 -167
- package/skills/scientific-thinking-literature-review/SKILL.md +193 -193
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -161
- package/skills/search-first/SKILL.md +183 -183
- package/skills/security-bounty-hunter/SKILL.md +100 -100
- package/skills/security-review/SKILL.md +504 -504
- package/skills/security-scan/SKILL.md +166 -166
- package/skills/seo/SKILL.md +155 -155
- package/skills/skill-comply/SKILL.md +59 -59
- package/skills/skill-scout/SKILL.md +141 -141
- package/skills/skill-stocktake/SKILL.md +195 -195
- package/skills/social-graph-ranker/SKILL.md +155 -155
- package/skills/social-publisher/SKILL.md +130 -130
- package/skills/springboot-patterns/SKILL.md +315 -315
- package/skills/springboot-security/SKILL.md +273 -273
- package/skills/springboot-tdd/SKILL.md +159 -159
- package/skills/springboot-verification/SKILL.md +232 -232
- package/skills/strategic-compact/SKILL.md +142 -142
- package/skills/swift-actor-persistence/SKILL.md +144 -144
- package/skills/swift-concurrency-6-2/SKILL.md +216 -216
- package/skills/swift-protocol-di-testing/SKILL.md +191 -191
- package/skills/swiftui-patterns/SKILL.md +259 -259
- package/skills/taste/SKILL.md +264 -264
- package/skills/taste/references/genre-taxonomy.md +87 -87
- package/skills/tdd-workflow/SKILL.md +583 -583
- package/skills/team-agent-orchestration/SKILL.md +111 -111
- package/skills/team-builder/SKILL.md +169 -169
- package/skills/terminal-ops/SKILL.md +110 -110
- package/skills/tinystruct-patterns/SKILL.md +279 -279
- package/skills/tinystruct-patterns/references/architecture.md +90 -90
- package/skills/tinystruct-patterns/references/data-handling.md +60 -60
- package/skills/tinystruct-patterns/references/database.md +99 -99
- package/skills/tinystruct-patterns/references/routing.md +64 -64
- package/skills/tinystruct-patterns/references/system-usage.md +97 -97
- package/skills/tinystruct-patterns/references/testing.md +72 -72
- package/skills/token-budget-advisor/SKILL.md +134 -134
- package/skills/ui-demo/SKILL.md +466 -466
- package/skills/ui-to-vue/SKILL.md +135 -135
- package/skills/uncloud/SKILL.md +344 -344
- package/skills/unified-memory/SKILL.md +170 -170
- package/skills/unified-notifications-ops/SKILL.md +188 -188
- package/skills/verification-loop/SKILL.md +129 -129
- package/skills/video-editing/SKILL.md +311 -311
- package/skills/videodb/SKILL.md +375 -375
- package/skills/visa-doc-translate/SKILL.md +117 -117
- package/skills/vite-patterns/SKILL.md +450 -450
- package/skills/vue-patterns/SKILL.md +471 -471
- package/skills/windows-desktop-e2e/SKILL.md +888 -888
- package/skills/workspace-surface-audit/SKILL.md +126 -126
- package/skills/x-api/SKILL.md +235 -235
package/lib/mcp.js
CHANGED
|
@@ -1,258 +1,258 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A small Model Context Protocol client: enough to connect to MCP servers,
|
|
3
|
-
* list their tools and call them. No SDK dependency; MCP is JSON-RPC 2.0
|
|
4
|
-
* over either a child process's stdio (one JSON message per line) or HTTP
|
|
5
|
-
* ("streamable HTTP": POST a request, get JSON or an SSE stream back).
|
|
6
|
-
*
|
|
7
|
-
* Servers are configured in the same shape Claude Desktop / Cursor use, so an
|
|
8
|
-
* existing config can be pasted in:
|
|
9
|
-
*
|
|
10
|
-
* ~/.codeply/mcp.json (every project)
|
|
11
|
-
* <project>/.codeply/mcp.json (this project; same name wins)
|
|
12
|
-
*
|
|
13
|
-
* { "mcpServers": {
|
|
14
|
-
* "github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_TOKEN": "..." } },
|
|
15
|
-
* "docs": { "url": "https://example.com/mcp", "headers": { "Authorization": "Bearer ..." } },
|
|
16
|
-
* "old": { "command": "...", "disabled": true } } }
|
|
17
|
-
*
|
|
18
|
-
* Connections are kept for the life of the process and reused across turns.
|
|
19
|
-
*/
|
|
20
|
-
const fs = require('fs');
|
|
21
|
-
const os = require('os');
|
|
22
|
-
const path = require('path');
|
|
23
|
-
const { spawn } = require('child_process');
|
|
24
|
-
const plugins = require('./plugins.js');
|
|
25
|
-
|
|
26
|
-
const PROTOCOL_VERSION = '2025-06-18';
|
|
27
|
-
const CONNECT_TIMEOUT_MS = 20000;
|
|
28
|
-
const CALL_TIMEOUT_MS = 120000;
|
|
29
|
-
const CLIENT_INFO = { name: 'codeply', version: '1.0' };
|
|
30
|
-
|
|
31
|
-
function readConfig(file) {
|
|
32
|
-
try {
|
|
33
|
-
const j = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
34
|
-
return j.mcpServers || j.servers || {};
|
|
35
|
-
} catch { return {}; }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Merged server config for a project: project entries override global ones. */
|
|
39
|
-
function loadServers(cwd) {
|
|
40
|
-
const merged = { ...plugins.mcpServers(cwd), ...readConfig(path.join(os.homedir(), '.codeply', 'mcp.json')) };
|
|
41
|
-
if (cwd) Object.assign(merged, readConfig(path.join(cwd, '.codeply', 'mcp.json')));
|
|
42
|
-
return Object.fromEntries(Object.entries(merged).filter(([, s]) => s && !s.disabled && (s.command || s.url)));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ─── Transports ─────────────────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
class StdioConnection {
|
|
48
|
-
constructor(name, spec, cwd) {
|
|
49
|
-
this.name = name;
|
|
50
|
-
this.pending = new Map();
|
|
51
|
-
this.nextId = 1;
|
|
52
|
-
this.stderr = '';
|
|
53
|
-
// npx/npm/uvx are .cmd shims on Windows and only start through a shell.
|
|
54
|
-
// A full path to a program (which may contain spaces) is started
|
|
55
|
-
// directly. Through the shell, arguments are quoted here, since Node
|
|
56
|
-
// just joins them with spaces.
|
|
57
|
-
const cmd = String(spec.command);
|
|
58
|
-
const args = (spec.args || []).map(String);
|
|
59
|
-
const viaShell = process.platform === 'win32' && !/[\\/]/.test(cmd) && !/\.exe$/i.test(cmd);
|
|
60
|
-
const quote = (a) => (/[\s"&|<>^()]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a);
|
|
61
|
-
this.proc = spawn(viaShell ? [cmd, ...args].map(quote).join(' ') : cmd, viaShell ? [] : args, {
|
|
62
|
-
cwd: spec.cwd || cwd || process.cwd(),
|
|
63
|
-
env: { ...process.env, ...(spec.env || {}) },
|
|
64
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
65
|
-
windowsHide: true,
|
|
66
|
-
shell: viaShell,
|
|
67
|
-
});
|
|
68
|
-
let buf = '';
|
|
69
|
-
this.proc.stdout.on('data', (d) => {
|
|
70
|
-
buf += d.toString('utf8');
|
|
71
|
-
let nl;
|
|
72
|
-
while ((nl = buf.indexOf('\n')) !== -1) {
|
|
73
|
-
const line = buf.slice(0, nl).trim();
|
|
74
|
-
buf = buf.slice(nl + 1);
|
|
75
|
-
if (!line) continue;
|
|
76
|
-
let msg;
|
|
77
|
-
try { msg = JSON.parse(line); } catch { continue; } // servers sometimes log to stdout
|
|
78
|
-
this.onMessage(msg);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
this.proc.stderr.on('data', (d) => { this.stderr = (this.stderr + d.toString('utf8')).slice(-4000); });
|
|
82
|
-
this.proc.on('error', (e) => this.failAll(`could not start: ${e.message}`));
|
|
83
|
-
this.proc.on('exit', (code) => { this.closed = true; this.failAll(`exited (code ${code})${this.stderr ? `: ${this.stderr.trim().split('\n').slice(-3).join(' ')}` : ''}`); });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
onMessage(msg) {
|
|
87
|
-
if (msg.id !== undefined && this.pending.has(msg.id)) {
|
|
88
|
-
const p = this.pending.get(msg.id);
|
|
89
|
-
this.pending.delete(msg.id);
|
|
90
|
-
if (msg.error) p.reject(new Error(msg.error.message || JSON.stringify(msg.error)));
|
|
91
|
-
else p.resolve(msg.result);
|
|
92
|
-
} else if (msg.method && msg.id !== undefined) {
|
|
93
|
-
// A request from the server (sampling, roots...): we offer none of those.
|
|
94
|
-
this.write({ jsonrpc: '2.0', id: msg.id, error: { code: -32601, message: 'Not supported by this client' } });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
write(obj) {
|
|
99
|
-
if (!this.closed) this.proc.stdin.write(JSON.stringify(obj) + '\n');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
request(method, params, timeoutMs) {
|
|
103
|
-
return new Promise((resolve, reject) => {
|
|
104
|
-
if (this.closed) return reject(new Error(`${this.name} is not running`));
|
|
105
|
-
const id = this.nextId++;
|
|
106
|
-
const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`${method} timed out after ${timeoutMs / 1000}s`)); }, timeoutMs);
|
|
107
|
-
this.pending.set(id, { resolve: (v) => { clearTimeout(timer); resolve(v); }, reject: (e) => { clearTimeout(timer); reject(e); } });
|
|
108
|
-
this.write({ jsonrpc: '2.0', id, method, params });
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
notify(method, params) { this.write({ jsonrpc: '2.0', method, ...(params ? { params } : {}) }); }
|
|
113
|
-
|
|
114
|
-
failAll(why) {
|
|
115
|
-
for (const [, p] of this.pending) p.reject(new Error(`${this.name} ${why}`));
|
|
116
|
-
this.pending.clear();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
close() { try { this.proc.kill(); } catch {} }
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
class HttpConnection {
|
|
123
|
-
constructor(name, spec) {
|
|
124
|
-
this.name = name;
|
|
125
|
-
this.url = spec.url;
|
|
126
|
-
this.headers = spec.headers || {};
|
|
127
|
-
this.sessionId = null;
|
|
128
|
-
this.nextId = 1;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async post(body, timeoutMs) {
|
|
132
|
-
const res = await fetch(this.url, {
|
|
133
|
-
method: 'POST',
|
|
134
|
-
headers: {
|
|
135
|
-
'Content-Type': 'application/json',
|
|
136
|
-
Accept: 'application/json, text/event-stream',
|
|
137
|
-
'MCP-Protocol-Version': PROTOCOL_VERSION,
|
|
138
|
-
...(this.sessionId ? { 'Mcp-Session-Id': this.sessionId } : {}),
|
|
139
|
-
...this.headers,
|
|
140
|
-
},
|
|
141
|
-
body: JSON.stringify(body),
|
|
142
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
143
|
-
});
|
|
144
|
-
const sid = res.headers.get('mcp-session-id');
|
|
145
|
-
if (sid) this.sessionId = sid;
|
|
146
|
-
return res;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async request(method, params, timeoutMs) {
|
|
150
|
-
const id = this.nextId++;
|
|
151
|
-
const res = await this.post({ jsonrpc: '2.0', id, method, params }, timeoutMs);
|
|
152
|
-
if (!res.ok) throw new Error(`${method}: HTTP ${res.status}${res.status === 401 ? ' (needs authorization - add a token under "headers")' : ''}`);
|
|
153
|
-
const type = res.headers.get('content-type') || '';
|
|
154
|
-
let msg = null;
|
|
155
|
-
if (type.includes('text/event-stream')) {
|
|
156
|
-
// Read events until the one answering our id.
|
|
157
|
-
const text = await res.text();
|
|
158
|
-
for (const block of text.split(/\r?\n\r?\n/)) {
|
|
159
|
-
const data = block.split(/\r?\n/).filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trim()).join('\n');
|
|
160
|
-
if (!data) continue;
|
|
161
|
-
try { const m = JSON.parse(data); if (m.id === id) { msg = m; break; } } catch {}
|
|
162
|
-
}
|
|
163
|
-
} else {
|
|
164
|
-
msg = await res.json().catch(() => null);
|
|
165
|
-
}
|
|
166
|
-
if (!msg) throw new Error(`${method}: no response`);
|
|
167
|
-
if (msg.error) throw new Error(msg.error.message || JSON.stringify(msg.error));
|
|
168
|
-
return msg.result;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async notify(method, params) {
|
|
172
|
-
try { await this.post({ jsonrpc: '2.0', method, ...(params ? { params } : {}) }, 10000); } catch {}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
close() {}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ─── Servers ────────────────────────────────────────────────────────────────
|
|
179
|
-
|
|
180
|
-
const servers = new Map(); // key -> { name, conn, tools, error, specKey }
|
|
181
|
-
|
|
182
|
-
function specKey(spec) { return JSON.stringify(spec); }
|
|
183
|
-
|
|
184
|
-
async function connect(name, spec, cwd) {
|
|
185
|
-
const conn = spec.url ? new HttpConnection(name, spec) : new StdioConnection(name, spec, cwd);
|
|
186
|
-
try {
|
|
187
|
-
const init = await conn.request('initialize', { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: CLIENT_INFO }, CONNECT_TIMEOUT_MS);
|
|
188
|
-
await conn.notify('notifications/initialized');
|
|
189
|
-
const tools = [];
|
|
190
|
-
let cursor;
|
|
191
|
-
do {
|
|
192
|
-
const page = await conn.request('tools/list', cursor ? { cursor } : {}, CONNECT_TIMEOUT_MS);
|
|
193
|
-
tools.push(...(page.tools || []));
|
|
194
|
-
cursor = page.nextCursor;
|
|
195
|
-
} while (cursor && tools.length < 500);
|
|
196
|
-
return { name, conn, tools, instructions: init?.instructions || '', error: null };
|
|
197
|
-
} catch (e) {
|
|
198
|
-
conn.close();
|
|
199
|
-
return { name, conn: null, tools: [], instructions: '', error: e.message };
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Connect to every configured server for this project (reusing live
|
|
205
|
-
* connections) and return what they offer. Never throws.
|
|
206
|
-
* @returns {Promise<Array<{name, tools, error, instructions}>>}
|
|
207
|
-
*/
|
|
208
|
-
async function listServers(cwd) {
|
|
209
|
-
const wanted = loadServers(cwd);
|
|
210
|
-
const out = [];
|
|
211
|
-
await Promise.all(Object.entries(wanted).map(async ([name, spec]) => {
|
|
212
|
-
const key = `${cwd}::${name}`;
|
|
213
|
-
let s = servers.get(key);
|
|
214
|
-
const stale = s && (s.specKey !== specKey(spec) || (s.conn && s.conn.closed) || s.error);
|
|
215
|
-
if (!s || stale) {
|
|
216
|
-
if (s?.conn) s.conn.close();
|
|
217
|
-
s = { ...(await connect(name, spec, cwd)), specKey: specKey(spec) };
|
|
218
|
-
servers.set(key, s);
|
|
219
|
-
}
|
|
220
|
-
out.push({ name, tools: s.tools, error: s.error, instructions: s.instructions });
|
|
221
|
-
}));
|
|
222
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/** Tool results as plain text for the model, images kept aside as data URLs. */
|
|
226
|
-
function flattenResult(result) {
|
|
227
|
-
const parts = Array.isArray(result?.content) ? result.content : [];
|
|
228
|
-
const text = [];
|
|
229
|
-
const images = [];
|
|
230
|
-
for (const p of parts) {
|
|
231
|
-
if (p.type === 'text') text.push(p.text);
|
|
232
|
-
else if (p.type === 'image' && p.data) images.push(`data:${p.mimeType || 'image/png'};base64,${p.data}`);
|
|
233
|
-
else if (p.type === 'resource') text.push(p.resource?.text || `[resource ${p.resource?.uri || ''}]`);
|
|
234
|
-
else text.push(`[${p.type} content]`);
|
|
235
|
-
}
|
|
236
|
-
if (!parts.length && result?.structuredContent) text.push(JSON.stringify(result.structuredContent, null, 1));
|
|
237
|
-
return { text: text.join('\n') || '(empty result)', images, isError: !!result?.isError };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async function callTool(cwd, serverName, toolName, args) {
|
|
241
|
-
const s = servers.get(`${cwd}::${serverName}`);
|
|
242
|
-
if (!s || !s.conn) throw new Error(`MCP server "${serverName}" is not connected${s?.error ? ` (${s.error})` : ''}.`);
|
|
243
|
-
const result = await s.conn.request('tools/call', { name: toolName, arguments: args || {} }, CALL_TIMEOUT_MS);
|
|
244
|
-
return flattenResult(result);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function findTool(cwd, serverName, toolName) {
|
|
248
|
-
const s = servers.get(`${cwd}::${serverName}`);
|
|
249
|
-
return s ? s.tools.find((t) => t.name === toolName) || null : null;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function closeAll() {
|
|
253
|
-
for (const [, s] of servers) if (s.conn) s.conn.close();
|
|
254
|
-
servers.clear();
|
|
255
|
-
}
|
|
256
|
-
process.on('exit', closeAll);
|
|
257
|
-
|
|
258
|
-
module.exports = { listServers, callTool, findTool, loadServers, closeAll, flattenResult };
|
|
1
|
+
/**
|
|
2
|
+
* A small Model Context Protocol client: enough to connect to MCP servers,
|
|
3
|
+
* list their tools and call them. No SDK dependency; MCP is JSON-RPC 2.0
|
|
4
|
+
* over either a child process's stdio (one JSON message per line) or HTTP
|
|
5
|
+
* ("streamable HTTP": POST a request, get JSON or an SSE stream back).
|
|
6
|
+
*
|
|
7
|
+
* Servers are configured in the same shape Claude Desktop / Cursor use, so an
|
|
8
|
+
* existing config can be pasted in:
|
|
9
|
+
*
|
|
10
|
+
* ~/.codeply/mcp.json (every project)
|
|
11
|
+
* <project>/.codeply/mcp.json (this project; same name wins)
|
|
12
|
+
*
|
|
13
|
+
* { "mcpServers": {
|
|
14
|
+
* "github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_TOKEN": "..." } },
|
|
15
|
+
* "docs": { "url": "https://example.com/mcp", "headers": { "Authorization": "Bearer ..." } },
|
|
16
|
+
* "old": { "command": "...", "disabled": true } } }
|
|
17
|
+
*
|
|
18
|
+
* Connections are kept for the life of the process and reused across turns.
|
|
19
|
+
*/
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const os = require('os');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { spawn } = require('child_process');
|
|
24
|
+
const plugins = require('./plugins.js');
|
|
25
|
+
|
|
26
|
+
const PROTOCOL_VERSION = '2025-06-18';
|
|
27
|
+
const CONNECT_TIMEOUT_MS = 20000;
|
|
28
|
+
const CALL_TIMEOUT_MS = 120000;
|
|
29
|
+
const CLIENT_INFO = { name: 'codeply', version: '1.0' };
|
|
30
|
+
|
|
31
|
+
function readConfig(file) {
|
|
32
|
+
try {
|
|
33
|
+
const j = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
34
|
+
return j.mcpServers || j.servers || {};
|
|
35
|
+
} catch { return {}; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Merged server config for a project: project entries override global ones. */
|
|
39
|
+
function loadServers(cwd) {
|
|
40
|
+
const merged = { ...plugins.mcpServers(cwd), ...readConfig(path.join(os.homedir(), '.codeply', 'mcp.json')) };
|
|
41
|
+
if (cwd) Object.assign(merged, readConfig(path.join(cwd, '.codeply', 'mcp.json')));
|
|
42
|
+
return Object.fromEntries(Object.entries(merged).filter(([, s]) => s && !s.disabled && (s.command || s.url)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Transports ─────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
class StdioConnection {
|
|
48
|
+
constructor(name, spec, cwd) {
|
|
49
|
+
this.name = name;
|
|
50
|
+
this.pending = new Map();
|
|
51
|
+
this.nextId = 1;
|
|
52
|
+
this.stderr = '';
|
|
53
|
+
// npx/npm/uvx are .cmd shims on Windows and only start through a shell.
|
|
54
|
+
// A full path to a program (which may contain spaces) is started
|
|
55
|
+
// directly. Through the shell, arguments are quoted here, since Node
|
|
56
|
+
// just joins them with spaces.
|
|
57
|
+
const cmd = String(spec.command);
|
|
58
|
+
const args = (spec.args || []).map(String);
|
|
59
|
+
const viaShell = process.platform === 'win32' && !/[\\/]/.test(cmd) && !/\.exe$/i.test(cmd);
|
|
60
|
+
const quote = (a) => (/[\s"&|<>^()]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a);
|
|
61
|
+
this.proc = spawn(viaShell ? [cmd, ...args].map(quote).join(' ') : cmd, viaShell ? [] : args, {
|
|
62
|
+
cwd: spec.cwd || cwd || process.cwd(),
|
|
63
|
+
env: { ...process.env, ...(spec.env || {}) },
|
|
64
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
65
|
+
windowsHide: true,
|
|
66
|
+
shell: viaShell,
|
|
67
|
+
});
|
|
68
|
+
let buf = '';
|
|
69
|
+
this.proc.stdout.on('data', (d) => {
|
|
70
|
+
buf += d.toString('utf8');
|
|
71
|
+
let nl;
|
|
72
|
+
while ((nl = buf.indexOf('\n')) !== -1) {
|
|
73
|
+
const line = buf.slice(0, nl).trim();
|
|
74
|
+
buf = buf.slice(nl + 1);
|
|
75
|
+
if (!line) continue;
|
|
76
|
+
let msg;
|
|
77
|
+
try { msg = JSON.parse(line); } catch { continue; } // servers sometimes log to stdout
|
|
78
|
+
this.onMessage(msg);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
this.proc.stderr.on('data', (d) => { this.stderr = (this.stderr + d.toString('utf8')).slice(-4000); });
|
|
82
|
+
this.proc.on('error', (e) => this.failAll(`could not start: ${e.message}`));
|
|
83
|
+
this.proc.on('exit', (code) => { this.closed = true; this.failAll(`exited (code ${code})${this.stderr ? `: ${this.stderr.trim().split('\n').slice(-3).join(' ')}` : ''}`); });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
onMessage(msg) {
|
|
87
|
+
if (msg.id !== undefined && this.pending.has(msg.id)) {
|
|
88
|
+
const p = this.pending.get(msg.id);
|
|
89
|
+
this.pending.delete(msg.id);
|
|
90
|
+
if (msg.error) p.reject(new Error(msg.error.message || JSON.stringify(msg.error)));
|
|
91
|
+
else p.resolve(msg.result);
|
|
92
|
+
} else if (msg.method && msg.id !== undefined) {
|
|
93
|
+
// A request from the server (sampling, roots...): we offer none of those.
|
|
94
|
+
this.write({ jsonrpc: '2.0', id: msg.id, error: { code: -32601, message: 'Not supported by this client' } });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
write(obj) {
|
|
99
|
+
if (!this.closed) this.proc.stdin.write(JSON.stringify(obj) + '\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
request(method, params, timeoutMs) {
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
if (this.closed) return reject(new Error(`${this.name} is not running`));
|
|
105
|
+
const id = this.nextId++;
|
|
106
|
+
const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(`${method} timed out after ${timeoutMs / 1000}s`)); }, timeoutMs);
|
|
107
|
+
this.pending.set(id, { resolve: (v) => { clearTimeout(timer); resolve(v); }, reject: (e) => { clearTimeout(timer); reject(e); } });
|
|
108
|
+
this.write({ jsonrpc: '2.0', id, method, params });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
notify(method, params) { this.write({ jsonrpc: '2.0', method, ...(params ? { params } : {}) }); }
|
|
113
|
+
|
|
114
|
+
failAll(why) {
|
|
115
|
+
for (const [, p] of this.pending) p.reject(new Error(`${this.name} ${why}`));
|
|
116
|
+
this.pending.clear();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
close() { try { this.proc.kill(); } catch {} }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class HttpConnection {
|
|
123
|
+
constructor(name, spec) {
|
|
124
|
+
this.name = name;
|
|
125
|
+
this.url = spec.url;
|
|
126
|
+
this.headers = spec.headers || {};
|
|
127
|
+
this.sessionId = null;
|
|
128
|
+
this.nextId = 1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async post(body, timeoutMs) {
|
|
132
|
+
const res = await fetch(this.url, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
Accept: 'application/json, text/event-stream',
|
|
137
|
+
'MCP-Protocol-Version': PROTOCOL_VERSION,
|
|
138
|
+
...(this.sessionId ? { 'Mcp-Session-Id': this.sessionId } : {}),
|
|
139
|
+
...this.headers,
|
|
140
|
+
},
|
|
141
|
+
body: JSON.stringify(body),
|
|
142
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
143
|
+
});
|
|
144
|
+
const sid = res.headers.get('mcp-session-id');
|
|
145
|
+
if (sid) this.sessionId = sid;
|
|
146
|
+
return res;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async request(method, params, timeoutMs) {
|
|
150
|
+
const id = this.nextId++;
|
|
151
|
+
const res = await this.post({ jsonrpc: '2.0', id, method, params }, timeoutMs);
|
|
152
|
+
if (!res.ok) throw new Error(`${method}: HTTP ${res.status}${res.status === 401 ? ' (needs authorization - add a token under "headers")' : ''}`);
|
|
153
|
+
const type = res.headers.get('content-type') || '';
|
|
154
|
+
let msg = null;
|
|
155
|
+
if (type.includes('text/event-stream')) {
|
|
156
|
+
// Read events until the one answering our id.
|
|
157
|
+
const text = await res.text();
|
|
158
|
+
for (const block of text.split(/\r?\n\r?\n/)) {
|
|
159
|
+
const data = block.split(/\r?\n/).filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trim()).join('\n');
|
|
160
|
+
if (!data) continue;
|
|
161
|
+
try { const m = JSON.parse(data); if (m.id === id) { msg = m; break; } } catch {}
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
msg = await res.json().catch(() => null);
|
|
165
|
+
}
|
|
166
|
+
if (!msg) throw new Error(`${method}: no response`);
|
|
167
|
+
if (msg.error) throw new Error(msg.error.message || JSON.stringify(msg.error));
|
|
168
|
+
return msg.result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async notify(method, params) {
|
|
172
|
+
try { await this.post({ jsonrpc: '2.0', method, ...(params ? { params } : {}) }, 10000); } catch {}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
close() {}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ─── Servers ────────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
const servers = new Map(); // key -> { name, conn, tools, error, specKey }
|
|
181
|
+
|
|
182
|
+
function specKey(spec) { return JSON.stringify(spec); }
|
|
183
|
+
|
|
184
|
+
async function connect(name, spec, cwd) {
|
|
185
|
+
const conn = spec.url ? new HttpConnection(name, spec) : new StdioConnection(name, spec, cwd);
|
|
186
|
+
try {
|
|
187
|
+
const init = await conn.request('initialize', { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: CLIENT_INFO }, CONNECT_TIMEOUT_MS);
|
|
188
|
+
await conn.notify('notifications/initialized');
|
|
189
|
+
const tools = [];
|
|
190
|
+
let cursor;
|
|
191
|
+
do {
|
|
192
|
+
const page = await conn.request('tools/list', cursor ? { cursor } : {}, CONNECT_TIMEOUT_MS);
|
|
193
|
+
tools.push(...(page.tools || []));
|
|
194
|
+
cursor = page.nextCursor;
|
|
195
|
+
} while (cursor && tools.length < 500);
|
|
196
|
+
return { name, conn, tools, instructions: init?.instructions || '', error: null };
|
|
197
|
+
} catch (e) {
|
|
198
|
+
conn.close();
|
|
199
|
+
return { name, conn: null, tools: [], instructions: '', error: e.message };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Connect to every configured server for this project (reusing live
|
|
205
|
+
* connections) and return what they offer. Never throws.
|
|
206
|
+
* @returns {Promise<Array<{name, tools, error, instructions}>>}
|
|
207
|
+
*/
|
|
208
|
+
async function listServers(cwd) {
|
|
209
|
+
const wanted = loadServers(cwd);
|
|
210
|
+
const out = [];
|
|
211
|
+
await Promise.all(Object.entries(wanted).map(async ([name, spec]) => {
|
|
212
|
+
const key = `${cwd}::${name}`;
|
|
213
|
+
let s = servers.get(key);
|
|
214
|
+
const stale = s && (s.specKey !== specKey(spec) || (s.conn && s.conn.closed) || s.error);
|
|
215
|
+
if (!s || stale) {
|
|
216
|
+
if (s?.conn) s.conn.close();
|
|
217
|
+
s = { ...(await connect(name, spec, cwd)), specKey: specKey(spec) };
|
|
218
|
+
servers.set(key, s);
|
|
219
|
+
}
|
|
220
|
+
out.push({ name, tools: s.tools, error: s.error, instructions: s.instructions });
|
|
221
|
+
}));
|
|
222
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Tool results as plain text for the model, images kept aside as data URLs. */
|
|
226
|
+
function flattenResult(result) {
|
|
227
|
+
const parts = Array.isArray(result?.content) ? result.content : [];
|
|
228
|
+
const text = [];
|
|
229
|
+
const images = [];
|
|
230
|
+
for (const p of parts) {
|
|
231
|
+
if (p.type === 'text') text.push(p.text);
|
|
232
|
+
else if (p.type === 'image' && p.data) images.push(`data:${p.mimeType || 'image/png'};base64,${p.data}`);
|
|
233
|
+
else if (p.type === 'resource') text.push(p.resource?.text || `[resource ${p.resource?.uri || ''}]`);
|
|
234
|
+
else text.push(`[${p.type} content]`);
|
|
235
|
+
}
|
|
236
|
+
if (!parts.length && result?.structuredContent) text.push(JSON.stringify(result.structuredContent, null, 1));
|
|
237
|
+
return { text: text.join('\n') || '(empty result)', images, isError: !!result?.isError };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function callTool(cwd, serverName, toolName, args) {
|
|
241
|
+
const s = servers.get(`${cwd}::${serverName}`);
|
|
242
|
+
if (!s || !s.conn) throw new Error(`MCP server "${serverName}" is not connected${s?.error ? ` (${s.error})` : ''}.`);
|
|
243
|
+
const result = await s.conn.request('tools/call', { name: toolName, arguments: args || {} }, CALL_TIMEOUT_MS);
|
|
244
|
+
return flattenResult(result);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function findTool(cwd, serverName, toolName) {
|
|
248
|
+
const s = servers.get(`${cwd}::${serverName}`);
|
|
249
|
+
return s ? s.tools.find((t) => t.name === toolName) || null : null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function closeAll() {
|
|
253
|
+
for (const [, s] of servers) if (s.conn) s.conn.close();
|
|
254
|
+
servers.clear();
|
|
255
|
+
}
|
|
256
|
+
process.on('exit', closeAll);
|
|
257
|
+
|
|
258
|
+
module.exports = { listServers, callTool, findTool, loadServers, closeAll, flattenResult };
|