Skip to content

Commit 918ad08

Browse files
authored
Merge pull request #131 from wherka-ama/feature/mcp-remote-servers-duplicate-detection
feat(mcp): remote servers and duplicate detection
2 parents 7fbf0f7 + e3eb2c7 commit 918ad08

20 files changed

+2040
-47
lines changed

docs/author-guide/collection-schema.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,29 @@ items: # Required. List of resources (max 50)
2929

3030
mcp: # Optional. MCP server configurations
3131
items:
32+
# Stdio server (local process)
3233
python-analyzer: # Server name
33-
command: python # Required. Command to start server
34+
type: stdio # Optional. Default: stdio
35+
command: python # Required for stdio. Command to start server
3436
args: # Optional. Command arguments
3537
- "${bundlePath}/server.py" # ${bundlePath} = installed bundle path
3638
env: # Optional. Environment variables
3739
LOG_LEVEL: info
40+
envFile: "${bundlePath}/.env" # Optional. Path to env file
3841
disabled: false # Optional. Default: false
42+
description: Python analyzer # Optional. Human-readable description
43+
44+
# Remote HTTP server
45+
api-server:
46+
type: http # Required for remote. One of: http, sse
47+
url: "https://api.example.com/mcp" # Required for remote
48+
headers: # Optional. Authentication headers
49+
Authorization: "Bearer ${env:API_TOKEN}"
50+
51+
# Remote SSE server
52+
streaming-server:
53+
type: sse
54+
url: "https://stream.example.com/mcp/events"
3955

4056
display: # Optional. UI preferences
4157
color: "#3776AB" # Color theme
@@ -44,6 +60,24 @@ display: # Optional. UI preferences
4460
show_badge: true # Show badge in UI
4561
```
4662
63+
## MCP Server Duplicate Detection
64+
65+
When multiple collections define the same MCP server, Prompt Registry automatically detects and manages duplicates to prevent conflicts in VS Code's `mcp.json`.
66+
67+
### How It Works
68+
69+
**Server Identity** is computed based on server type:
70+
- **Stdio servers**: `command` + `args` (e.g., `node server.js --port 3000`)
71+
- **Remote servers**: `url` (e.g., `https://api.example.com/mcp`)
72+
73+
**Behavior**:
74+
1. First installed server with a given identity remains **enabled**
75+
2. Subsequent duplicates are **disabled** with a description noting the original
76+
3. When the active server's bundle is uninstalled, a disabled duplicate is **re-enabled**
77+
4. At least one instance stays active until all bundles with that server are removed
78+
79+
This allows multiple collections to safely share common MCP servers without conflicts.
80+
4781
## Validation
4882

4983
Run `Ctrl+Shift+P` → "Prompt Registry: Validate Collections"

docs/contributor-guide/architecture/mcp-integration.md

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,34 @@ graph TD
2626
D --> E
2727
```
2828

29-
## Bundle Manifest Schema
29+
## Server Types
30+
31+
### Stdio Servers (Local Process)
3032

3133
```yaml
3234
mcpServers:
3335
server-name:
36+
type: stdio # Optional (default)
3437
command: string # Required
3538
args: string[] # Optional
3639
env: Record<string, string> # Optional
40+
envFile: string # Optional - path to .env file
3741
disabled: boolean # Optional (default: false)
3842
description: string # Optional
3943
```
4044
45+
### Remote Servers (HTTP/SSE)
46+
47+
```yaml
48+
mcpServers:
49+
api-server:
50+
type: http # Required: 'http' or 'sse'
51+
url: string # Required - supports http://, https://, unix://, pipe://
52+
headers: Record<string, string> # Optional - for authentication
53+
disabled: boolean # Optional
54+
description: string # Optional
55+
```
56+
4157
## Variable Substitution
4258
4359
| Variable | Description |
@@ -68,6 +84,64 @@ mcpServers:
6884
3. Update tracking metadata
6985
4. Atomic operations with backup/rollback
7086

87+
## Duplicate Detection Algorithm
88+
89+
When multiple bundles define the same MCP server, duplicates are automatically detected and disabled.
90+
91+
### Server Identity Computation
92+
93+
```typescript
94+
computeServerIdentity(config: McpServerConfig): string {
95+
if (isRemoteServerConfig(config)) {
96+
return `remote:${config.url}`;
97+
} else {
98+
const argsStr = config.args?.join('|') || '';
99+
return `stdio:${config.command}:${argsStr}`;
100+
}
101+
}
102+
```
103+
104+
| Server Type | Identity Format | Example |
105+
|-------------|-----------------|----------|
106+
| Stdio | `stdio:{command}:{args joined by \|}` | `stdio:node:server.js\|--port\|3000` |
107+
| Remote | `remote:{url}` | `remote:https://api.example.com/mcp` |
108+
109+
### Detection Flow
110+
111+
```mermaid
112+
graph TD
113+
A["After server installation"]
114+
B["detectAndDisableDuplicates()"]
115+
C["For each server in mcp.json"]
116+
D{"Identity already seen?"}
117+
E["Record identity → server mapping"]
118+
F["Mark as disabled\nAdd description: 'Duplicate of X'"]
119+
G["Write updated config"]
120+
121+
A --> B
122+
B --> C
123+
C --> D
124+
D -->|No| E
125+
D -->|Yes & enabled| F
126+
E --> C
127+
F --> C
128+
C -->|Done| G
129+
```
130+
131+
### Lifecycle Behavior
132+
133+
1. **Install**: First server with identity stays enabled; duplicates disabled
134+
2. **Uninstall**: When active server's bundle is removed, remaining duplicates are re-evaluated
135+
3. **Invariant**: At least one server per identity remains active until all bundles are removed
136+
137+
### Type Guards
138+
139+
```typescript
140+
// Discriminate server types
141+
isStdioServerConfig(config) // true if has 'command', no 'url'
142+
isRemoteServerConfig(config) // true if has 'url' and type is 'http'|'sse'
143+
```
144+
71145
## See Also
72146

73147
- [Installation Flow](./installation-flow.md) — Bundle installation

lib/bin/generate-manifest.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ try {
117117
.replace(/^@[^/]+\//, '');
118118
}
119119

120+
// Extract MCP servers from either 'mcp.items' or 'mcpServers' field (matching AwesomeCopilotAdapter)
121+
const mcpServers = collection.mcpServers || (collection.mcp && collection.mcp.items);
122+
120123
// Create deployment manifest
121124
const manifest = {
122125
id: manifestId,
@@ -130,6 +133,7 @@ try {
130133
repository: packageJson.repository?.url?.replace(/^git\+/, '').replace(/\.git$/, '') || '',
131134
prompts: prompts,
132135
dependencies: [],
136+
...(mcpServers && Object.keys(mcpServers).length > 0 ? { mcpServers } : {}),
133137
};
134138

135139
// Write deployment manifest
@@ -150,6 +154,11 @@ try {
150154
const typeLabel = type.charAt(0).toUpperCase() + type.slice(1) + 's';
151155
console.log(` ${typeLabel}: ${typesCounts[type]}`);
152156
});
157+
158+
// Log MCP servers count if present
159+
if (mcpServers && Object.keys(mcpServers).length > 0) {
160+
console.log(` MCP Servers: ${Object.keys(mcpServers).length}`);
161+
}
153162
} catch (error) {
154163
console.error('❌ Error generating deployment manifest:', error.message);
155164
process.exit(1);

lib/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@prompt-registry/collection-scripts",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"description": "Shared scripts for building, validating, and publishing Copilot prompt collections",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

0 commit comments

Comments
 (0)