Skip to content

Commit 66ca418

Browse files
authored
refactor: restructure packages.Package model for better extensibility (#131)
- Remove Runtimes field from Package struct (derive from installations) - Move Version field from Package level to Installation level - Add Transport support with stdio/sse/streamable-http types - Add Deprecated field to Installation struct - Update mcpm provider to use new structure with version="latest" - Update filter providers to derive runtimes from installations map - Update package printer to show runtimes from installations - Fix all test data to use Installation-level Version field
1 parent 04eb752 commit 66ca418

File tree

10 files changed

+241
-60
lines changed

10 files changed

+241
-60
lines changed

cmd/add.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,8 @@ func parseServerEntry(
225225
}
226226

227227
v := "latest"
228-
if pkg.Version != "" {
229-
v = pkg.Version
228+
if installation, ok := pkg.Installations[selectedRuntime]; ok && installation.Version != "" {
229+
v = installation.Version
230230
}
231231

232232
runtimeSpecificName := pkg.Installations[selectedRuntime].Package

cmd/add_test.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ func TestAddCmd_Success(t *testing.T) {
8181
{Name: "toolA"},
8282
{Name: "toolB"},
8383
},
84-
Version: "1.2.3",
8584
Installations: map[runtime.Runtime]packages.Installation{
8685
runtime.UVX: {
8786
Command: "uvx",
8887
Package: "mcp-server-1",
88+
Version: "1.2.3",
8989
Recommended: true,
9090
},
9191
},
@@ -142,9 +142,8 @@ func TestAddCmd_BasicServerAdd(t *testing.T) {
142142
o := &bytes.Buffer{}
143143

144144
pkg := packages.Package{
145-
ID: "testserver",
146-
Name: "testserver",
147-
Version: "latest",
145+
ID: "testserver",
146+
Name: "testserver",
148147
Tools: []packages.Tool{
149148
{Name: "tool1"},
150149
{Name: "tool2"},
@@ -154,6 +153,7 @@ func TestAddCmd_BasicServerAdd(t *testing.T) {
154153
"uvx": {
155154
Command: "uvx",
156155
Package: "mcp-server-testserver",
156+
Version: "latest",
157157
Recommended: true,
158158
},
159159
},
@@ -200,9 +200,8 @@ func TestAddCmd_ServerWithArguments(t *testing.T) {
200200
{
201201
name: "server with all argument types",
202202
pkg: packages.Package{
203-
ID: "github-server",
204-
Name: "GitHub Server",
205-
Version: "1.0.0",
203+
ID: "github-server",
204+
Name: "GitHub Server",
206205
Tools: []packages.Tool{
207206
{Name: "create_repo"},
208207
{Name: "list_repos"},
@@ -211,6 +210,7 @@ func TestAddCmd_ServerWithArguments(t *testing.T) {
211210
runtime.UVX: {
212211
Command: "uvx",
213212
Package: "mcp-server-github",
213+
Version: "1.0.0",
214214
Recommended: true,
215215
},
216216
},
@@ -230,16 +230,16 @@ func TestAddCmd_ServerWithArguments(t *testing.T) {
230230
{
231231
name: "server with only env vars",
232232
pkg: packages.Package{
233-
ID: "db-server",
234-
Name: "Database Server",
235-
Version: "2.0.0",
233+
ID: "db-server",
234+
Name: "Database Server",
236235
Tools: []packages.Tool{
237236
{Name: "query"},
238237
},
239238
Installations: map[runtime.Runtime]packages.Installation{
240239
runtime.UVX: {
241240
Command: "uvx",
242241
Package: "mcp-server-db",
242+
Version: "2.0.0",
243243
Recommended: true,
244244
},
245245
},
@@ -255,16 +255,16 @@ func TestAddCmd_ServerWithArguments(t *testing.T) {
255255
{
256256
name: "server with mixed value and bool args",
257257
pkg: packages.Package{
258-
ID: "api-server",
259-
Name: "API Server",
260-
Version: "3.0.0",
258+
ID: "api-server",
259+
Name: "API Server",
261260
Tools: []packages.Tool{
262261
{Name: "call_api"},
263262
},
264263
Installations: map[runtime.Runtime]packages.Installation{
265264
runtime.UVX: {
266265
Command: "uvx",
267266
Package: "mcp-server-api",
267+
Version: "3.0.0",
268268
Recommended: true,
269269
},
270270
},
@@ -282,16 +282,16 @@ func TestAddCmd_ServerWithArguments(t *testing.T) {
282282
{
283283
name: "server with no required arguments",
284284
pkg: packages.Package{
285-
ID: "simple-server",
286-
Name: "Simple Server",
287-
Version: "1.0.0",
285+
ID: "simple-server",
286+
Name: "Simple Server",
288287
Tools: []packages.Tool{
289288
{Name: "hello"},
290289
},
291290
Installations: map[runtime.Runtime]packages.Installation{
292291
runtime.UVX: {
293292
Command: "uvx",
294293
Package: "mcp-server-simple",
294+
Version: "1.0.0",
295295
Recommended: true,
296296
},
297297
},

cmd/search_test.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,11 @@ func TestSearchCmd_DefaultFormat(t *testing.T) {
144144
Tools: []packages.Tool{
145145
{Name: "test_tool"},
146146
},
147-
Runtimes: []runtime.Runtime{runtime.UVX},
147+
Installations: packages.Installations{
148+
runtime.UVX: packages.Installation{
149+
Command: "test-server",
150+
},
151+
},
148152
}
149153

150154
o := new(bytes.Buffer)
@@ -176,7 +180,11 @@ func TestSearchCmd_TextFormat(t *testing.T) {
176180
Tools: []packages.Tool{
177181
{Name: "test_tool"},
178182
},
179-
Runtimes: []runtime.Runtime{runtime.UVX},
183+
Installations: packages.Installations{
184+
runtime.UVX: packages.Installation{
185+
Command: "test-server",
186+
},
187+
},
180188
}
181189

182190
o := new(bytes.Buffer)
@@ -208,7 +216,11 @@ func TestSearchCmd_JSONFormat(t *testing.T) {
208216
Tools: []packages.Tool{
209217
{Name: "test_tool"},
210218
},
211-
Runtimes: []runtime.Runtime{runtime.UVX},
219+
Installations: packages.Installations{
220+
runtime.UVX: packages.Installation{
221+
Command: "test-server",
222+
},
223+
},
212224
}
213225

214226
o := new(bytes.Buffer)
@@ -272,7 +284,11 @@ func TestSearchCmd_JSONFormat_MultipleResults(t *testing.T) {
272284
Tools: []packages.Tool{
273285
{Name: "tool1"},
274286
},
275-
Runtimes: []runtime.Runtime{runtime.UVX},
287+
Installations: packages.Installations{
288+
runtime.UVX: packages.Installation{
289+
Command: "test-server",
290+
},
291+
},
276292
}
277293

278294
pkg2 := packages.Package{
@@ -284,7 +300,11 @@ func TestSearchCmd_JSONFormat_MultipleResults(t *testing.T) {
284300
Tools: []packages.Tool{
285301
{Name: "tool2"},
286302
},
287-
Runtimes: []runtime.Runtime{runtime.Docker},
303+
Installations: packages.Installations{
304+
runtime.Docker: packages.Installation{
305+
Command: "test-server",
306+
},
307+
},
288308
}
289309

290310
fakeReg := &fakeRegistryMultiple{packages: []packages.Package{pkg1, pkg2}}
@@ -340,7 +360,11 @@ func TestSearchCmd_CaseInsensitiveFormat(t *testing.T) {
340360
Tools: []packages.Tool{
341361
{Name: "test_tool"},
342362
},
343-
Runtimes: []runtime.Runtime{runtime.UVX},
363+
Installations: packages.Installations{
364+
runtime.UVX: packages.Installation{
365+
Command: "test-server",
366+
},
367+
},
344368
}
345369

346370
testCases := []struct {
@@ -474,7 +498,11 @@ func TestSearchCmd_FlagsWithJSONFormat(t *testing.T) {
474498
Tools: []packages.Tool{
475499
{Name: "test_tool"},
476500
},
477-
Runtimes: []runtime.Runtime{runtime.UVX},
501+
Installations: packages.Installations{
502+
runtime.UVX: packages.Installation{
503+
Command: "test-server",
504+
},
505+
},
478506
}
479507

480508
o := new(bytes.Buffer)
@@ -510,7 +538,11 @@ func TestSearchCmd_WildcardSearch(t *testing.T) {
510538
Tools: []packages.Tool{
511539
{Name: "test_tool"},
512540
},
513-
Runtimes: []runtime.Runtime{runtime.UVX},
541+
Installations: packages.Installations{
542+
runtime.UVX: packages.Installation{
543+
Command: "test-server",
544+
},
545+
},
514546
}
515547

516548
o := new(bytes.Buffer)

internal/packages/installation.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ type Installation struct {
88
Command string `json:"command"`
99
Args []string `json:"args"`
1010
Package string `json:"package,omitempty"`
11+
Version string `json:"version,omitempty"`
1112
Env map[string]string `json:"env,omitempty"`
1213
Description string `json:"description,omitempty"`
1314
Recommended bool `json:"recommended,omitempty"`
1415
Deprecated bool `json:"deprecated,omitempty"`
16+
Transports []Transport `json:"transports,omitempty"`
1517
}
1618

1719
// AnyDeprecated can be used to determine if any of the installations are deprecated.

internal/packages/package.go

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
package packages
22

3-
import "github.com/mozilla-ai/mcpd/v2/internal/runtime"
4-
53
// Package represents a canonical, flattened view of a discoverable MCP Server package.
64
type Package struct {
7-
ID string `json:"id"`
8-
Name string `json:"name"`
9-
DisplayName string `json:"displayName"`
10-
Description string `json:"description"`
11-
License string `json:"license"`
12-
Tools Tools `json:"tools"`
13-
Tags []string `json:"tags"`
14-
Categories []string `json:"categories"`
15-
Runtimes []runtime.Runtime `json:"runtimes"`
16-
Installations Installations `json:"installations"`
17-
Arguments Arguments `json:"arguments"`
18-
Source string `json:"source"`
19-
Version string `json:"version"`
20-
Transport string `json:"transport"` // TODO: Default to stdio.
21-
IsOfficial bool `json:"isOfficial"` // TODO: Not all registries support this.
5+
ID string `json:"id"`
6+
Name string `json:"name"`
7+
DisplayName string `json:"displayName"`
8+
Description string `json:"description"`
9+
License string `json:"license"`
10+
Tools Tools `json:"tools"`
11+
Tags []string `json:"tags"`
12+
Categories []string `json:"categories"`
13+
Installations Installations `json:"installations"`
14+
Arguments Arguments `json:"arguments"`
15+
Source string `json:"source"`
16+
Transports []Transport `json:"transports"`
17+
IsOfficial bool `json:"isOfficial"`
18+
Deprecated bool `json:"deprecated"`
2219
}

internal/packages/transport.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package packages
2+
3+
// Transport represents the supported transport mechanisms for MCP servers.
4+
type Transport string
5+
6+
const (
7+
// TransportStdio represents standard input/output transport (default).
8+
// This is the most common transport used by MCP servers.
9+
TransportStdio Transport = "stdio"
10+
11+
// TransportSSE represents SSE transport.
12+
TransportSSE Transport = "sse"
13+
14+
// TransportStreamableHTTP represents streamable-HTTP (websocket) transport.
15+
TransportStreamableHTTP Transport = "streamable-http"
16+
)
17+
18+
// AllTransports returns all supported transport types.
19+
func AllTransports() []Transport {
20+
return []Transport{
21+
TransportStdio,
22+
TransportSSE,
23+
TransportStreamableHTTP,
24+
}
25+
}
26+
27+
// DefaultTransports returns the default transports that most MCP servers support.
28+
// By convention, all MCP servers support stdio transport.
29+
func DefaultTransports() []Transport {
30+
return []Transport{TransportStdio}
31+
}
32+
33+
// ToStrings converts a slice of Transport to a slice of strings.
34+
func ToStrings(transports []Transport) []string {
35+
result := make([]string, len(transports))
36+
for i, transport := range transports {
37+
result[i] = string(transport)
38+
}
39+
return result
40+
}
41+
42+
// FromStrings converts a slice of strings to a slice of Transport.
43+
// Unknown transport types are skipped.
44+
func FromStrings(transportStrs []string) []Transport {
45+
var result []Transport
46+
validTransports := map[string]Transport{
47+
string(TransportStdio): TransportStdio,
48+
string(TransportSSE): TransportSSE,
49+
string(TransportStreamableHTTP): TransportStreamableHTTP,
50+
}
51+
52+
for _, str := range transportStrs {
53+
if transport, ok := validTransports[str]; ok {
54+
result = append(result, transport)
55+
}
56+
}
57+
58+
// Always ensure stdio is included if no valid transports found
59+
if len(result) == 0 {
60+
result = DefaultTransports()
61+
}
62+
63+
return result
64+
}
65+
66+
// HasTransport checks if a slice of transports contains a specific transport.
67+
func HasTransport(transports []Transport, transport Transport) bool {
68+
for _, t := range transports {
69+
if t == transport {
70+
return true
71+
}
72+
}
73+
return false
74+
}

0 commit comments

Comments
 (0)