Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 0 additions & 41 deletions .changesets/breaking_nc_config_file.md

This file was deleted.

3 changes: 0 additions & 3 deletions .changesets/feat_pubmodmatt_minify.md

This file was deleted.

3 changes: 0 additions & 3 deletions .changesets/feat_pubmodmatt_search.md

This file was deleted.

3 changes: 0 additions & 3 deletions .changesets/feat_validate_tool.md

This file was deleted.

7 changes: 7 additions & 0 deletions .github/workflows/release-bins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ jobs:
tar -C release/$RELEASE -cf - dist/ | gzip -9 > artifacts/apollo-mcp-server-$VERSION-$RENAMED.tar.gz
done

# We only need to generate the config schema for a release once, so we do it
# on the linux host since it is the cheapest.
- name: Generate config schema
if: ${{ matrix.bundle == 'linux' }}
run: |
./release/x86_64-unknown-linux-musl/dist/config-schema > artifacts/config.schema.json

- name: Upload release artifacts
uses: softprops/action-gh-release@v2
with:
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/release-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ jobs:
docker manifest annotate $FQDN:latest $FQDN:$VERSION-amd64 --arch amd64
docker manifest annotate $FQDN:latest $FQDN:$VERSION-arm64 --arch arm64
- name: Push the multiarch manifests
shell: bash
run: |
docker manifest push $FQDN:$VERSION
docker manifest push $FQDN:latest

# Only push the latest tag if this isn't a release candidate (ends with
# `rc.#`.
if [[ ! "$VERSION" =~ -rc\.[0-9]+$ ]]; then
docker manifest push $FQDN:latest
fi
72 changes: 72 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,78 @@ All notable changes to this project will be documented in this file.

This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# [0.6.0] - 2025-07-14

## ❗ BREAKING ❗

### Replace CLI flags with a configuration file - @nicholascioli PR #162

All command line arguments are now removed and replaced with equivalent configuration
options. The Apollo MCP server only accepts a single argument which is a path to a
configuration file. An empty file may be passed, as all options have sane defaults
that follow the previous argument defaults.

All options can be overridden by environment variables. They are of the following
form:

- Prefixed by `APOLLO_MCP_`
- Suffixed by the config equivalent path, with `__` marking nested options.

E.g. The environment variable to change the config option `introspection.execute.enabled`
would be `APOLLO_MCP_INTROSPECTION__EXECUTE__ENABLED`.

Below is a valid configuration file with some options filled out:

```yaml
custom_scalars: /path/to/custom/scalars
endpoint: http://127.0.0.1:4000
graphos:
apollo_key: some.key
apollo_graph_ref: example@graph
headers:
X-Some-Header: example-value
introspection:
execute:
enabled: true
introspect:
enabled: false
logging:
level: info
operations:
source: local
paths:
- /path/to/operation.graphql
- /path/to/other/operation.graphql
overrides:
disable_type_description: false
disable_schema_description: false
enable_explorer: false
mutation_mode: all
schema:
source: local
path: /path/to/schema.graphql
transport:
type: streamable_http
address: 127.0.0.1
port: 5000
```

## 🚀 Features

### Validate tool for verifying graphql queries before executing them - @swcollard PR #203

The introspection options in the mcp server provide introspect, execute, and search tools. The LLM often tries to validate its queries by just executing them. This may not be desired (there might be side effects, for example). This feature adds a `validate` tool so the LLM can validate the operation without actually hitting the GraphQL endpoint. It first validates the syntax of the operation, and then checks it against the introspected schema for validation.

### Minify introspect return value - @pubmodmatt PR #178

The `introspect` and `search` tools now have an option to minify results. Minified GraphQL SDL takes up less space in the context window.

### Add search tool - @pubmodmatt PR #171

A new experimental `search` tool has been added that allows the AI model to specify a set of terms to search for in the GraphQL schema. The top types matching that search are returned, as well as enough information to enable creation of GraphQL operations involving those types.



# [0.5.2] - 2025-07-10

## 🐛 Fixes
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ members = [

[workspace.package]
authors = ["Apollo <[email protected]>"]
version = "0.5.2"
version = "0.6.0"

[workspace.dependencies]
apollo-compiler = "1.27.0"
Expand Down
13 changes: 7 additions & 6 deletions crates/apollo-mcp-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,16 @@ const STYLES: Styles = Styles::styled()
)]
struct Args {
/// Path to the config file
config: PathBuf,
config: Option<PathBuf>,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config: runtime::Config = {
let args = Args::parse();
runtime::read_config(args.config)?
};
let config: runtime::Config = Args::parse()
.config
.map(runtime::read_config)
.transpose()?
.unwrap_or_default();

let mut env_filter = EnvFilter::from_default_env().add_directive(config.logging.level.into());

Expand Down Expand Up @@ -134,7 +135,7 @@ async fn main() -> anyhow::Result<()> {
.transport(config.transport)
.schema_source(schema_source)
.operation_source(operation_source)
.endpoint(config.endpoint)
.endpoint(config.endpoint.into_inner())
.maybe_explorer_graph_ref(explorer_graph_ref)
.headers(config.headers)
.execute_introspection(config.introspection.execute.enabled)
Expand Down
1 change: 1 addition & 0 deletions crates/apollo-mcp-server/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! related to runtime configuration.

mod config;
mod endpoint;
mod graphos;
mod introspection;
mod logging;
Expand Down
41 changes: 7 additions & 34 deletions crates/apollo-mcp-server/src/runtime/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,75 +7,48 @@ use serde::Deserialize;
use url::Url;

use super::{
OperationSource, SchemaSource, graphos::GraphOSConfig, introspection::Introspection,
logging::Logging, overrides::Overrides,
OperationSource, SchemaSource, endpoint::Endpoint, graphos::GraphOSConfig,
introspection::Introspection, logging::Logging, overrides::Overrides,
};

/// Configuration for the MCP server
#[derive(Debug, Deserialize, JsonSchema)]
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[serde(default)]
pub struct Config {
/// Path to a custom scalar map
pub custom_scalars: Option<PathBuf>,

/// The target GraphQL endpoint
#[serde(default = "defaults::endpoint")]
pub endpoint: Url,
#[schemars(schema_with = "Url::json_schema")]
pub endpoint: Endpoint,

/// Apollo-specific credential overrides
#[serde(default)]
pub graphos: GraphOSConfig,

/// List of hard-coded headers to include in all GraphQL requests
#[serde(default, deserialize_with = "parsers::map_from_str")]
#[serde(deserialize_with = "parsers::map_from_str")]
#[schemars(schema_with = "super::schemas::header_map")]
pub headers: HeaderMap,

/// Introspection configuration
#[serde(default)]
pub introspection: Introspection,

/// Logging configuration
#[serde(default)]
pub logging: Logging,

/// Operations
#[serde(default)]
pub operations: OperationSource,

/// Overrides for server behaviour
#[serde(default)]
pub overrides: Overrides,

/// The schema to load for operations
#[serde(default)]
pub schema: SchemaSource,

/// The type of server transport to use
#[serde(default)]
pub transport: Transport,
}

mod defaults {
use url::Url;

pub(super) fn endpoint() -> Url {
// SAFETY: This should always parse correctly and is considered a breaking
// error otherwise. It is also explicitly tested in [test::default_endpoint_parses_correctly]
#[allow(clippy::unwrap_used)]
Url::parse("http://127.0.0.1:4000").unwrap()
}

#[cfg(test)]
mod test {
use super::endpoint;

#[test]
fn default_endpoint_parses_correctly() {
endpoint();
}
}
}

mod parsers {
use std::str::FromStr;

Expand Down
67 changes: 67 additions & 0 deletions crates/apollo-mcp-server/src/runtime/endpoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Endpoint newtype
//!
//! This module defines a simple newtype around a Url for demarking a GraphQL
//! endpoint. This allows overlaying validation and default behaviour on top
//! of the wrapped URL.

use std::ops::Deref;

use serde::Deserialize;
use url::Url;

/// A GraphQL endpoint
#[derive(Debug)]
pub struct Endpoint(Url);

impl Endpoint {
/// Unwrap the endpoint into its inner URL
pub fn into_inner(self) -> Url {
self.0
}
}

impl Default for Endpoint {
fn default() -> Self {
Self(defaults::endpoint())
}
}

impl<'de> Deserialize<'de> for Endpoint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// This is a simple wrapper around URL, so we just use its deserializer
let url = Url::deserialize(deserializer)?;
Ok(Self(url))
}
}

impl Deref for Endpoint {
type Target = Url;

fn deref(&self) -> &Self::Target {
&self.0
}
}

mod defaults {
use url::Url;

pub(super) fn endpoint() -> Url {
// SAFETY: This should always parse correctly and is considered a breaking
// error otherwise. It is also explicitly tested in [test::default_endpoint_parses_correctly]
#[allow(clippy::unwrap_used)]
Url::parse("http://127.0.0.1:4000").unwrap()
}

#[cfg(test)]
mod test {
use super::endpoint;

#[test]
fn default_endpoint_parses_correctly() {
endpoint();
}
}
}
2 changes: 1 addition & 1 deletion docs/source/best-practices.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ In particular, we strongly recommend contract variants when using:

If you register a persisted query with a specific client name instead of `null`, you must configure the MCP Server to send the necessary header indicating the client name to the router.

Use the `--header` option when running the MCP Server to pass the header to the router. The default name of the header expected by the router is `apollographql-client-name`. To use a different header name, configure `telemetry.apollo.client_name_header` in router YAML configuration.
Use the `headers` option when running the MCP Server to pass the header to the router. The default name of the header expected by the router is `apollographql-client-name`. To use a different header name, configure `telemetry.apollo.client_name_header` in router YAML configuration.

## Avoid token passthrough for authentication

Expand Down
Loading