Skip to content

Commit bf69e6c

Browse files
authored
Registry: add Mozilla AI registry provider with embedded data (#132)
* Registry: add Mozilla AI registry provider with embedded data * Add mozilla-ai provider with complete registry implementation * Generated JSON schema for registry validation * Integration with basecmd to add mozilla-ai as primary registry (for add/resolve) * MCPM: drop manually setting version to latest (leave that for the caller - e.g. add) * Update API tools model * Registry JSON: add sqllite * Update Models * Drop args/env from Package.Installations * Drop args.env from Mozilla-ai's Server.Installations * Drop args/env from MCPM registry server installations * Update tests * Improve logger line for startup validation failure
1 parent 66ca418 commit bf69e6c

20 files changed

+2929
-81
lines changed

internal/api/tools.go

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,39 +21,100 @@ type ToolCallResponse struct {
2121

2222
// Tool represents a callable tool, following the MCP spec.
2323
type Tool struct {
24-
Name string `json:"name"`
25-
Description string `json:"description,omitempty"`
26-
InputSchema ToolInputSchema `json:"inputSchema"`
27-
Annotations ToolAnnotation `json:"annotations"`
24+
// Name of the tool.
25+
// Display name precedence order for a tool is: title, annotations.title, then name.
26+
Name string `json:"name"`
27+
28+
// Title is a human-readable and easily understood title for the tool.
29+
Title string `json:"title,omitempty"`
30+
31+
// Description is a human-readable description of the tool.
32+
// This can be used by clients to improve the LLM's understanding of available tools.
33+
// It can be thought of like a "hint" to the model.
34+
Description string `json:"description"`
35+
36+
// InputSchema is JSONSchema defining the expected parameters for the tool.
37+
InputSchema *JSONSchema `json:"inputSchema,omitempty"`
38+
39+
// OutputSchema is an optional JSONSchema defining the structure of the tool's
40+
// output returned in the structured content field of a tool call result.
41+
OutputSchema *JSONSchema `json:"outputSchema,omitempty"`
42+
43+
// Annotations provide optional additional tool information.
44+
// Display name precedence order is: title, annotations.title when present, then tool name.
45+
Annotations *ToolAnnotations `json:"annotations,omitempty"`
46+
47+
// Meta is reserved by MCP to allow clients and servers to attach additional metadata to their interactions.
48+
// See https://modelcontextprotocol.io/specification/2025-06-18/basic#general-fields for notes on _meta usage.
49+
Meta map[string]any `json:"_meta,omitempty"` //nolint:tagliatelle
2850
}
2951

3052
// ToolResponse represents the wrapped API response for a Tool.
3153
type ToolResponse struct {
3254
Body Tool
3355
}
3456

35-
// ToolInputSchema defines input params using JSON Schema
36-
type ToolInputSchema struct {
37-
Type string `json:"type"`
57+
// JSONSchema defines the structure for a JSON schema object.
58+
type JSONSchema struct {
59+
// Type defines the type for this schema, e.g. "object".
60+
Type string `json:"type"`
61+
62+
// Properties represents a property name and associated object definition.
3863
Properties map[string]any `json:"properties,omitempty"`
39-
Required []string `json:"required,omitempty"`
64+
65+
// Required lists the (keys of) Properties that are required.
66+
Required []string `json:"required,omitempty"`
4067
}
4168

42-
// ToolAnnotation defines behavioral hints for a tool
43-
type ToolAnnotation struct {
44-
Title string `json:"title,omitempty"`
45-
ReadOnlyHint *bool `json:"readOnlyHint,omitempty"`
46-
DestructiveHint *bool `json:"destructiveHint,omitempty"`
47-
IdempotentHint *bool `json:"idempotentHint,omitempty"`
48-
OpenWorldHint *bool `json:"openWorldHint,omitempty"`
69+
// ToolAnnotations provides additional properties describing a Tool to clients.
70+
// NOTE: all properties in ToolAnnotations are **hints**.
71+
// They are not guaranteed to provide a faithful description of tool behavior
72+
// (including descriptive properties like `title`).
73+
// Clients should never make tool use decisions based on ToolAnnotations received from untrusted servers.
74+
type ToolAnnotations struct {
75+
// Title is a human-readable title for the tool.
76+
Title *string `json:"title,omitempty"`
77+
78+
// ReadOnlyHint if true, the tool should not modify its environment.
79+
ReadOnlyHint *bool `json:"readOnlyHint,omitempty"`
80+
81+
// DestructiveHint if true, the tool may perform destructive updates to its environment.
82+
// If false, the tool performs only additive updates.
83+
// (This property is meaningful only when ReadOnlyHint is false)
84+
DestructiveHint *bool `json:"destructiveHint,omitempty"`
85+
86+
// IdempotentHint if true, calling the tool repeatedly with the same arguments
87+
// will have no additional effect on its environment.
88+
// (This property is meaningful only when ReadOnlyHint is false)
89+
IdempotentHint *bool `json:"idempotentHint,omitempty"`
90+
91+
// OpenWorldHint if true, this tool may interact with an "open world" of external
92+
// entities. If false, the tool's domain of interaction is closed.
93+
// For example, the world of a web search tool is open, whereas that
94+
// of a memory tool is not.
95+
OpenWorldHint *bool `json:"openWorldHint,omitempty"`
4996
}
5097

5198
// ToAPIType can be used to convert a wrapped domain type to an API-safe type.
5299
func (d DomainTool) ToAPIType() (Tool, error) {
100+
schema := &JSONSchema{
101+
Type: d.InputSchema.Type,
102+
Properties: d.InputSchema.Properties,
103+
Required: d.InputSchema.Required,
104+
}
105+
106+
annotations := &ToolAnnotations{
107+
Title: &d.Annotations.Title,
108+
ReadOnlyHint: d.Annotations.ReadOnlyHint,
109+
DestructiveHint: d.Annotations.DestructiveHint,
110+
OpenWorldHint: d.Annotations.OpenWorldHint,
111+
IdempotentHint: d.Annotations.IdempotentHint,
112+
}
113+
53114
return Tool{
54115
Name: d.Name,
55116
Description: d.Description,
56-
InputSchema: ToolInputSchema(d.InputSchema),
57-
Annotations: ToolAnnotation(d.Annotations),
117+
InputSchema: schema,
118+
Annotations: annotations,
58119
}, nil
59120
}

internal/cmd/basecmd.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/mozilla-ai/mcpd/v2/internal/cmd/output"
1111
"github.com/mozilla-ai/mcpd/v2/internal/flags"
1212
"github.com/mozilla-ai/mcpd/v2/internal/provider/mcpm"
13+
"github.com/mozilla-ai/mcpd/v2/internal/provider/mozilla_ai"
1314
"github.com/mozilla-ai/mcpd/v2/internal/registry"
1415
"github.com/mozilla-ai/mcpd/v2/internal/runtime"
1516
)
@@ -82,14 +83,20 @@ func (c *BaseCmd) Build() (registry.PackageProvider, error) {
8283
opts := runtime.WithSupportedRuntimes(supportedRuntimes...)
8384
l := logger.Named("registry")
8485

85-
mcpmRegistry, err := mcpm.NewRegistry(l, mcpm.ManifestURL, opts)
86+
mozillaRegistry, err := mozilla_ai.NewRegistry(l, "", opts)
8687
if err != nil {
8788
// TODO: Handle tolerating some failed registries, as long as we can meet a minimum requirement.
8889
return nil, err
8990
}
9091

92+
mcpmRegistry, err := mcpm.NewRegistry(l, mcpm.ManifestURL, opts)
93+
if err != nil {
94+
return nil, err
95+
}
96+
9197
// NOTE: The order the registries are added here determines their precedence when searching and resolving packages.
9298
registries := []registry.PackageProvider{
99+
mozillaRegistry,
93100
mcpmRegistry,
94101
}
95102

internal/daemon/daemon.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ func NewDaemon(apiAddr string, opts *Opts) (*Daemon, error) {
7777
serverNames = append(serverNames, srv.Name())
7878
// Validate the config since the daemon will be required to start MCP servers using it.
7979
if err := srv.Validate(); err != nil {
80-
validateErrs = errors.Join(validateErrs, err)
80+
validateErrs = errors.Join(
81+
validateErrs,
82+
fmt.Errorf("invalid server configuration '%s': %w", srv.Name(), err),
83+
)
8184
}
8285
}
8386
if validateErrs != nil {
@@ -110,6 +113,7 @@ func (d *Daemon) StartAndManage(ctx context.Context) error {
110113
d.logger.Info("Shutting down MCP servers and client connections")
111114
for _, n := range d.clientManager.List() {
112115
if c, ok := d.clientManager.Client(n); ok {
116+
d.logger.Info(fmt.Sprintf("Closing client %s", n))
113117
_ = c.Close()
114118
}
115119
}

internal/packages/argument.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ const (
1818
// VariableTypeArgBool represents a command line argument that is a boolean flag (doesn't have a value).
1919
VariableTypeArgBool VariableType = "argument_bool"
2020

21-
// VariableTypePositionalArg represents a positional command line argument.
22-
VariableTypePositionalArg VariableType = "positional_argument"
21+
// VariableTypeArgPositional represents a positional command line argument.
22+
VariableTypeArgPositional VariableType = "argument_positional"
2323
)
2424

2525
// EnvVarPlaceholderRegex is used to find environment variable placeholders like ${VAR_NAME}.
@@ -61,7 +61,7 @@ func (a Arguments) Ordered() []ArgumentMetadata {
6161
// Ensure name is set in the metadata
6262
meta.Name = name
6363

64-
if meta.VariableType == VariableTypePositionalArg && meta.Position != nil {
64+
if meta.VariableType == VariableTypeArgPositional && meta.Position != nil {
6565
positional = append(positional, meta)
6666
} else {
6767
others = append(others, meta)
@@ -129,7 +129,7 @@ func BoolArgument(_ string, data ArgumentMetadata) bool {
129129

130130
// PositionalArgument is a predicate that requires the argument is a positional command line argument.
131131
func PositionalArgument(_ string, data ArgumentMetadata) bool {
132-
return data.VariableType == VariableTypePositionalArg
132+
return data.VariableType == VariableTypeArgPositional
133133
}
134134

135135
// NonPositionalArgument is a predicate that requires the argument is not a positional command line argument.

internal/packages/argument_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -521,15 +521,15 @@ func TestArgumentMetadata_NameAndExample(t *testing.T) {
521521
Description: "Positional argument",
522522
Required: true,
523523
Example: "/path/to/file",
524-
VariableType: VariableTypePositionalArg,
524+
VariableType: VariableTypeArgPositional,
525525
Position: &[]int{1}[0],
526526
},
527527
expected: ArgumentMetadata{
528528
Name: "POS_ARG",
529529
Description: "Positional argument",
530530
Required: true,
531531
Example: "/path/to/file",
532-
VariableType: VariableTypePositionalArg,
532+
VariableType: VariableTypeArgPositional,
533533
Position: &[]int{1}[0],
534534
},
535535
},
@@ -568,7 +568,7 @@ func TestArguments_Ordered_NameSetting(t *testing.T) {
568568
Description: "Positional argument",
569569
Required: true,
570570
Example: "pos_example",
571-
VariableType: VariableTypePositionalArg,
571+
VariableType: VariableTypeArgPositional,
572572
Position: &[]int{1}[0],
573573
},
574574
"--flag": {
@@ -588,7 +588,7 @@ func TestArguments_Ordered_NameSetting(t *testing.T) {
588588
switch arg.Name {
589589
case "POS_ARG":
590590
require.Equal(t, "pos_example", arg.Example)
591-
require.Equal(t, VariableTypePositionalArg, arg.VariableType)
591+
require.Equal(t, VariableTypeArgPositional, arg.VariableType)
592592
require.NotNil(t, arg.Position)
593593
require.Equal(t, 1, *arg.Position)
594594
case "ENV_VAR":

internal/packages/installation.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@ import "github.com/mozilla-ai/mcpd/v2/internal/runtime"
55
type Installations map[runtime.Runtime]Installation
66

77
type Installation struct {
8-
Command string `json:"command"`
9-
Args []string `json:"args"`
10-
Package string `json:"package,omitempty"`
11-
Version string `json:"version,omitempty"`
12-
Env map[string]string `json:"env,omitempty"`
13-
Description string `json:"description,omitempty"`
14-
Recommended bool `json:"recommended,omitempty"`
15-
Deprecated bool `json:"deprecated,omitempty"`
16-
Transports []Transport `json:"transports,omitempty"`
8+
Command string `json:"command"`
9+
Package string `json:"package,omitempty"`
10+
Version string `json:"version,omitempty"`
11+
Description string `json:"description,omitempty"`
12+
Recommended bool `json:"recommended,omitempty"`
13+
Deprecated bool `json:"deprecated,omitempty"`
14+
Transports []Transport `json:"transports,omitempty"`
1715
}
1816

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

internal/packages/tools.go

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,6 @@ type Tool struct {
2020
// It can be thought of like a "hint" to the model.
2121
Description string `json:"description,omitempty"`
2222

23-
// InputSchema is JSONSchema defining the expected parameters for the tool.
24-
InputSchema JSONSchema `json:"inputSchema"`
25-
26-
// OutputSchema is an optional JSONSchema defining the structure of the tool's
27-
// output returned in the structured content field of a tool call result.
28-
OutputSchema *JSONSchema `json:"outputSchema,omitempty"`
29-
3023
// Annotations provide optional additional tool information.
3124
// Display name precedence order is: title, annotations.title when present, then tool name.
3225
Annotations *ToolAnnotations `json:"annotations,omitempty"`
@@ -36,18 +29,6 @@ type Tool struct {
3629
Meta map[string]any `json:"_meta,omitempty"`
3730
}
3831

39-
// JSONSchema defines the structure for a JSON schema object.
40-
type JSONSchema struct {
41-
// Type defines the type for this schema, e.g. "object".
42-
Type string `json:"type"`
43-
44-
// Properties represents a property name and associated object definition.
45-
Properties map[string]any `json:"properties,omitempty"`
46-
47-
// Required lists the (keys of) Properties that are required.
48-
Required []string `json:"required,omitempty"`
49-
}
50-
5132
// ToolAnnotations provides additional properties describing a Tool to clients.
5233
// NOTE: all properties in ToolAnnotations are **hints**.
5334
// They are not guaranteed to provide a faithful description of tool behavior

internal/provider/mcpm/cli_arg_parser.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (p *CLIArgParser) parsePositional(arg string, position int) {
5353

5454
// Store as positional argument with logical position information
5555
if metadata, exists := p.schema[placeholder]; exists {
56-
p.storeResultWithPosition(placeholder, packages.VariableTypePositionalArg, metadata, p.positionalCount)
56+
p.storeResultWithPosition(placeholder, packages.VariableTypeArgPositional, metadata, p.positionalCount)
5757
}
5858
}
5959

internal/provider/mcpm/registry.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,7 @@ func convertInstallations(
423423

424424
details[rt] = packages.Installation{
425425
Command: install.Command,
426-
Args: slices.Clone(install.Args),
427426
Package: pkg,
428-
Version: "latest", // MCPM doesn't support versions, so everything is 'latest'
429-
Env: maps.Clone(install.Env),
430427
Description: install.Description,
431428
Recommended: install.Recommended,
432429
Deprecated: false, // MCPM doesn't support deprecated installations
@@ -443,11 +440,6 @@ func (t Tool) ToDomainType() (packages.Tool, error) {
443440
Name: t.Name,
444441
Title: t.Title,
445442
Description: t.Description,
446-
InputSchema: packages.JSONSchema{
447-
Type: t.InputSchema.Type,
448-
Properties: t.InputSchema.Properties,
449-
Required: t.InputSchema.Required,
450-
},
451443
}, nil
452444
}
453445

0 commit comments

Comments
 (0)