Metadata-Version: 2.4
Name: age_mcp_server
Version: 0.3.0
Summary: Apache AGE MCP Server
Project-URL: Homepage, https://github.com/rioriost/age_mcp_server
Project-URL: Issues, https://github.com/rioriost/age_mcp_server/issues
Project-URL: Repository, https://github.com/rioriost/age_mcp_server
Project-URL: Changelog, https://github.com/rioriost/age_mcp_server/blob/main/CHANGELOG.md
Project-URL: Security, https://github.com/rioriost/age_mcp_server/security/policy
Author-email: Rio Fujita <rio_github@rio.st>
License: MIT License
        
        Copyright (c) 2025 Rio Fujita
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Requires-Python: >=3.13
Requires-Dist: agefreighter>=1.0.33
Requires-Dist: mcp<3,>=2.0.0
Requires-Dist: opentelemetry-api<2,>=1.44.0
Requires-Dist: ply>=3.11
Requires-Dist: psycopg-pool>=3.3.0
Requires-Dist: psycopg>=3.3.3
Provides-Extra: telemetry
Requires-Dist: opentelemetry-exporter-otlp<2,>=1.44.0; extra == 'telemetry'
Requires-Dist: opentelemetry-sdk<2,>=1.44.0; extra == 'telemetry'
Provides-Extra: test
Requires-Dist: pytest-cov>=7; extra == 'test'
Requires-Dist: pytest>=9; extra == 'test'
Description-Content-Type: text/markdown

# AGE MCP Server

[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
![Python](https://img.shields.io/badge/Python-3.13%2B-blue)

An MCP server for querying [Apache AGE](https://age.apache.org/) graphs in PostgreSQL.

Version 0.3.0 makes read-only operation the secure default, adds asynchronous
connection pooling, safe Cypher parameters and bounded cursor pagination, and
returns MCP structured content from every tool.

## Requirements

- Python 3.13 or later
- PostgreSQL with the Apache AGE extension installed and loaded
- A database role restricted to the graphs and operations the MCP client needs

Enable AGE in the target database:

```sql
CREATE EXTENSION IF NOT EXISTS age CASCADE;
```

## Install

With `uv`:

```bash
uv init your_project
cd your_project
uv add age_mcp_server
```

With a Python virtual environment:

```bash
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install age_mcp_server
```

With Homebrew:

```bash
brew install rioriost/tap/age_mcp_server
```

## Configure an MCP client

Avoid placing a database password in command-line arguments. Supply a connection
string through `PG_CONNECTION_STRING` and use one of libpq's credential mechanisms,
such as `PGPASSWORD` or a protected PostgreSQL password file.

```json
{
  "mcpServers": {
    "age_manager": {
      "command": "age_mcp_server",
      "env": {
        "PG_CONNECTION_STRING": "host=db.example port=5432 dbname=postgres user=age_reader sslmode=require",
        "PGPASSWORD": "replace-with-a-secret"
      }
    }
  }
}
```

Treat the MCP client configuration as a secret if it contains `PGPASSWORD`. A
PostgreSQL password file or the client's secret store is preferable.

The connection string can still be supplied explicitly when necessary:

```bash
age_mcp_server --pg-con-str "host=db.example dbname=postgres user=age_reader sslmode=require"
```

For Microsoft Entra authentication to Azure Database for PostgreSQL, first sign in
with the Azure CLI, then opt in to token acquisition:

```bash
age_mcp_server \
  --pg-con-str "host=server.postgres.database.azure.com dbname=postgres user=identity sslmode=require" \
  --azure-identity
```

## Tools

Read-only mode is the default:

| Tool | Purpose |
| --- | --- |
| `read-age-cypher` | Run a validated, parameterized, paginated read-only Cypher query |
| `list-age-graphs` | List Apache AGE graphs |
| `get-age-schema` | Inspect counts, directions, and sampled property types |

Write tools are only advertised and accepted when the server starts with
`--allow-write`:

| Tool | Purpose |
| --- | --- |
| `write-age-cypher` | Run Cypher containing a mutating clause |
| `create-age-graph` | Create a graph |
| `drop-age-graph` | Permanently drop a graph |

```bash
age_mcp_server --allow-write
```

Use a separate, least-privileged database role for write mode. Enabling the flag
does not grant PostgreSQL privileges that the configured role does not already have.

## Safety limits

- Cypher, graph names, return aliases, and graph-management arguments are safely
  quoted or parameterized before reaching PostgreSQL.
- Read tools run inside PostgreSQL read-only transactions.
- `CALL` is considered side-effecting and requires write mode.
- Read pages contain at most 50 rows. Opaque HMAC-authenticated cursors are bound to
  the graph, query, and parameters, with a maximum offset of 100,000.
- Cypher `$parameters` are accepted only when placeholder names exactly match a
  JSON parameter object. The object is capped at 100,000 bytes and passed to AGE
  through a prepared statement.
- Write queries execute fully and return an affected-row count instead of result
  rows.
- Statements time out after 30 seconds by default.
- Queries are limited to 100,000 characters and must contain one explicit `RETURN`
  clause per query branch.
- Raw database errors, query contents, and connection credentials are not returned
  to MCP clients or written to normal logs.

Change the timeout when needed:

```bash
age_mcp_server --statement-timeout-ms 60000
```

The timeout must be between 1 millisecond and 1 hour.

Tune the asynchronous connection pool or load the AGE library for every newly
opened pooled connection:

```bash
age_mcp_server --pool-min-size 2 --pool-max-size 8 --load-age
```

The pool must satisfy `1 <= min <= max <= 64`. `RETURN *` remains unsupported;
list return values explicitly so Apache AGE's SQL result types can be declared.

Example tool input with parameters and pagination:

```json
{
  "graph_name": "people",
  "query": "MATCH (n:Person) WHERE n.age >= $minimum RETURN n.name AS name ORDER BY name",
  "parameters": {"minimum": 18},
  "page_size": 25
}
```

Pass the returned `nextCursor` as `cursor` to fetch the next page.

## OpenTelemetry

Install the optional exporter dependencies and enable OTLP export:

```bash
python3 -m pip install "age_mcp_server[telemetry]"
age_mcp_server --enable-telemetry --otel-service-name age-production
```

The exporter follows standard `OTEL_EXPORTER_OTLP_*` environment variables.
Traces and metrics record operation latency, counts, and failures. Connection
details, Cypher text, parameter values, and raw database errors are excluded.

## Development

Install all development dependencies and run the release gate:

```bash
make sync
make check
```

`make check` runs Ruff, the 80% coverage gate, Bandit, the locked dependency audit,
and package builds. The test suite includes a live Apache AGE integration test:

```bash
AGE_TEST_CONNECTION_STRING="host=127.0.0.1 dbname=postgres user=postgres password=postgres" \
  make integration
```

CI runs this test against the official Apache AGE PostgreSQL container. See
[the 0.3.0 review](docs/release-0.3.0-review.md) for the completed security and
feature review.

## License

[MIT](LICENSE)
