Compare commits
113 Commits
0.1.0
...
ani/fix-np
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c04fa31e8 | ||
|
|
e700bc713a | ||
|
|
bea86af65b | ||
|
|
68a6130b17 | ||
|
|
853a3b4faf | ||
|
|
6f62066d34 | ||
|
|
c770d217e7 | ||
|
|
98470a12f9 | ||
|
|
a00564fafa | ||
|
|
62546dec58 | ||
|
|
886ac5fc7b | ||
|
|
722df4d798 | ||
|
|
407e304585 | ||
|
|
60578314aa | ||
|
|
3c4cb17d09 | ||
|
|
fbac5b78bc | ||
|
|
f876b1ec0d | ||
|
|
aecfa21d47 | ||
|
|
a3d542c0a3 | ||
|
|
2b79b6ffd4 | ||
|
|
1f28b4474c | ||
|
|
d69d67cb64 | ||
|
|
7792070d81 | ||
|
|
34a2843756 | ||
|
|
2a34770959 | ||
|
|
6b674b0827 | ||
|
|
ca8db1f417 | ||
|
|
eb4456d1e3 | ||
|
|
780b92274d | ||
|
|
b825784b8f | ||
|
|
52c7e98055 | ||
|
|
4862aa7c1d | ||
|
|
561ea91504 | ||
|
|
7c2be8d139 | ||
|
|
97d469911e | ||
|
|
11b891c6ca | ||
|
|
5139e723a4 | ||
|
|
fc5b79c9a6 | ||
|
|
47a87e1884 | ||
|
|
d567ff37e8 | ||
|
|
2e4eedc6ef | ||
|
|
56ec9befd9 | ||
|
|
360d090ac9 | ||
|
|
fda05836cb | ||
|
|
dac692e638 | ||
|
|
7e4b276f7f | ||
|
|
668448b047 | ||
|
|
4352c93660 | ||
|
|
22bf78720b | ||
|
|
9196c1ddaf | ||
|
|
dfc10488b2 | ||
|
|
78182eab10 | ||
|
|
7dbaee47a2 | ||
|
|
03193a9dc4 | ||
|
|
60dea9a868 | ||
|
|
1341d73775 | ||
|
|
f73770b143 | ||
|
|
33ab8dbd97 | ||
|
|
2d5c866f82 | ||
|
|
232d5ffcf6 | ||
|
|
4f930a61ab | ||
|
|
f684d2e891 | ||
|
|
676de45bab | ||
|
|
9a560e3f06 | ||
|
|
64d2fea0f1 | ||
|
|
1843562dce | ||
|
|
31630b2870 | ||
|
|
7c04df5e2e | ||
|
|
f8e26479d9 | ||
|
|
0c6b1ad1d2 | ||
|
|
b97acb9b76 | ||
|
|
1b06e50203 | ||
|
|
83abe4e00f | ||
|
|
75bc2d3f26 | ||
|
|
b127ba177a | ||
|
|
ce350433f9 | ||
|
|
c1a56810fb | ||
|
|
abff2486c1 | ||
|
|
b096f3f991 | ||
|
|
e97ec8dbc7 | ||
|
|
05c22f6367 | ||
|
|
3b3a7e6015 | ||
|
|
8986c32bb4 | ||
|
|
69b25300da | ||
|
|
6e72c7583e | ||
|
|
d1c9acffc2 | ||
|
|
d91d7e8ae0 | ||
|
|
6c27f5a263 | ||
|
|
2bf84a3ef3 | ||
|
|
8aad8b4aac | ||
|
|
06267d28f4 | ||
|
|
c1c8fc2f42 | ||
|
|
7a350785fe | ||
|
|
41f8ec0868 | ||
|
|
09d66ab704 | ||
|
|
e4faa19acb | ||
|
|
f7385dd961 | ||
|
|
54012aca6a | ||
|
|
0cf344bb6a | ||
|
|
a507bafc3e | ||
|
|
93b1ec4d61 | ||
|
|
bf2ddc9b7b | ||
|
|
da2ac8d423 | ||
|
|
3bae26723a | ||
|
|
9d0c643926 | ||
|
|
0716adafc6 | ||
|
|
733d2a6e6e | ||
|
|
ab9c130610 | ||
|
|
3e46011614 | ||
|
|
584c1076a4 | ||
|
|
de9ee3956e | ||
|
|
01fb48e6ef | ||
|
|
5ceaa48cf3 |
29
.github/workflows/main.yml
vendored
29
.github/workflows/main.yml
vendored
@@ -4,6 +4,8 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -21,3 +23,30 @@ jobs:
|
|||||||
# - run: npm ci
|
# - run: npm ci
|
||||||
- run: npm install --no-package-lock
|
- run: npm install --no-package-lock
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
|
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'release'
|
||||||
|
environment: release
|
||||||
|
needs: build
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: npm
|
||||||
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
|
# Working around https://github.com/npm/cli/issues/4828
|
||||||
|
# - run: npm ci
|
||||||
|
- run: npm install --no-package-lock
|
||||||
|
|
||||||
|
# TODO: Add --provenance once the repo is public
|
||||||
|
- run: npm run publish-all
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|||||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
mcp-coc@anthropic.com.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
||||||
37
CONTRIBUTING.md
Normal file
37
CONTRIBUTING.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Contributing to Model Context Protocol Inspector
|
||||||
|
|
||||||
|
Thanks for your interest in contributing! This guide explains how to get involved.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Fork the repository and clone it locally
|
||||||
|
2. Install dependencies with `npm install`
|
||||||
|
3. Run `npm run dev` to start both client and server in development mode
|
||||||
|
4. Use the web UI at http://localhost:5173 to interact with the inspector
|
||||||
|
|
||||||
|
## Development Process & Pull Requests
|
||||||
|
|
||||||
|
1. Create a new branch for your changes
|
||||||
|
2. Make your changes following existing code style and conventions
|
||||||
|
3. Test changes locally
|
||||||
|
4. Update documentation as needed
|
||||||
|
5. Use clear commit messages explaining your changes
|
||||||
|
6. Verify all changes work as expected
|
||||||
|
7. Submit a pull request
|
||||||
|
8. PRs will be reviewed by maintainers
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
This project follows our [Code of Conduct](CODE_OF_CONDUCT.md). Please read it before contributing.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
If you find a security vulnerability, please refer to our [Security Policy](SECURITY.md) for reporting instructions.
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
Feel free to [open an issue](https://github.com/modelcontextprotocol/mcp-inspector/issues) for questions or create a discussion for general topics.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing, you agree that your contributions will be licensed under the MIT license.
|
||||||
22
LICENSE
22
LICENSE
@@ -1,7 +1,21 @@
|
|||||||
Copyright (c) 2024 Anthropic, PBC.
|
MIT License
|
||||||
|
|
||||||
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:
|
Copyright (c) 2024 Anthropic, PBC
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
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 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.
|
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.
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -2,30 +2,47 @@
|
|||||||
|
|
||||||
The MCP inspector is a developer tool for testing and debugging MCP servers.
|
The MCP inspector is a developer tool for testing and debugging MCP servers.
|
||||||
|
|
||||||
## Getting started
|

|
||||||
|
|
||||||
This repository depends on the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk/). Until these repositories are made public and published to npm, the SDK has to be preinstalled manually:
|
## Running the Inspector
|
||||||
|
|
||||||
1. Download the [latest release of the SDK](https://github.com/modelcontextprotocol/typescript-sdk/releases) (the file named something like `modelcontextprotocol-sdk-0.1.0.tgz`). You don't need to extract it.
|
### From an MCP server repository
|
||||||
2. From within your checkout of _this_ repository, run `npm install --save path/to/sdk.tgz`. This will overwrite the expected location for the SDK to allow you to proceed.
|
|
||||||
|
|
||||||
Then, you should be able to install the rest of the dependencies normally:
|
To inspect an MCP server implementation, there's no need to clone this repo. Instead, use `npx`. For example, if your server is built at `build/index.js`:
|
||||||
|
|
||||||
```sh
|
```bash
|
||||||
npm install
|
npx @modelcontextprotocol/inspector build/index.js
|
||||||
```
|
```
|
||||||
|
|
||||||
You can run it in dev mode via:
|
You can also pass arguments along which will get passed as arguments to your MCP server:
|
||||||
|
|
||||||
|
```
|
||||||
|
npx @modelcontextprotocol/inspector build/index.js arg1 arg2 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
The inspector runs both a client UI (default port 5173) and an MCP proxy server (default port 3000). Open the client UI in your browser to use the inspector. You can customize the ports if needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CLIENT_PORT=8080 SERVER_PORT=9000 npx @modelcontextprotocol/inspector build/index.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### From this repository
|
||||||
|
|
||||||
|
If you're working on the inspector itself:
|
||||||
|
|
||||||
|
Development mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start both the client and server.
|
Production mode:
|
||||||
|
|
||||||
To run in production mode:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build
|
npm run build
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.
|
||||||
|
|||||||
14
SECURITY.md
Normal file
14
SECURITY.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Security Policy
|
||||||
|
Thank you for helping us keep the inspector secure.
|
||||||
|
|
||||||
|
## Reporting Security Issues
|
||||||
|
|
||||||
|
This project is maintained by [Anthropic](https://www.anthropic.com/) as part of the Model Context Protocol project.
|
||||||
|
|
||||||
|
The security of our systems and user data is Anthropic’s top priority. We appreciate the work of security researchers acting in good faith in identifying and reporting potential vulnerabilities.
|
||||||
|
|
||||||
|
Our security program is managed on HackerOne and we ask that any validated vulnerability in this functionality be reported through their [submission form](https://hackerone.com/anthropic-vdp/reports/new?type=team&report_type=vulnerability).
|
||||||
|
|
||||||
|
## Vulnerability Disclosure Program
|
||||||
|
|
||||||
|
Our Vulnerability Program Guidelines are defined on our [HackerOne program page](https://hackerone.com/anthropic-vdp).
|
||||||
102
bin/cli.js
102
bin/cli.js
@@ -1,36 +1,88 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { join, dirname } from "path";
|
import { resolve, dirname } from "path";
|
||||||
|
import { spawnPromise } from "spawn-rx";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import concurrently from "concurrently";
|
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
// Paths to the server and client entry points
|
function delay(ms) {
|
||||||
const serverPath = join(__dirname, "../server/build/index.js");
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
const clientPath = join(__dirname, "../client/bin/cli.js");
|
}
|
||||||
|
|
||||||
console.log("Starting MCP inspector...");
|
async function main() {
|
||||||
|
// Get command line arguments
|
||||||
|
const [, , command, ...mcpServerArgs] = process.argv;
|
||||||
|
|
||||||
const { result } = concurrently(
|
const inspectorServerPath = resolve(
|
||||||
[
|
__dirname,
|
||||||
|
"..",
|
||||||
|
"server",
|
||||||
|
"build",
|
||||||
|
"index.js",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Path to the client entry point
|
||||||
|
const inspectorClientPath = resolve(
|
||||||
|
__dirname,
|
||||||
|
"..",
|
||||||
|
"client",
|
||||||
|
"bin",
|
||||||
|
"cli.js",
|
||||||
|
);
|
||||||
|
|
||||||
|
const CLIENT_PORT = process.env.CLIENT_PORT ?? "5173";
|
||||||
|
const SERVER_PORT = process.env.SERVER_PORT ?? "3000";
|
||||||
|
|
||||||
|
console.log("Starting MCP inspector...");
|
||||||
|
|
||||||
|
const abort = new AbortController();
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
process.on("SIGINT", () => {
|
||||||
|
cancelled = true;
|
||||||
|
abort.abort();
|
||||||
|
});
|
||||||
|
|
||||||
|
const server = spawnPromise(
|
||||||
|
"node",
|
||||||
|
[
|
||||||
|
inspectorServerPath,
|
||||||
|
...(command ? [`--env`, command] : []),
|
||||||
|
...(mcpServerArgs ? ["--args", mcpServerArgs.join(" ")] : []),
|
||||||
|
],
|
||||||
{
|
{
|
||||||
command: `node ${serverPath}`,
|
env: { ...process.env, PORT: SERVER_PORT },
|
||||||
name: "server",
|
signal: abort.signal,
|
||||||
|
echoOutput: true,
|
||||||
},
|
},
|
||||||
{
|
);
|
||||||
command: `node ${clientPath}`,
|
|
||||||
name: "client",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
prefix: "name",
|
|
||||||
killOthers: ["failure", "success"],
|
|
||||||
restartTries: 3,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
result.catch((err) => {
|
const client = spawnPromise("node", [inspectorClientPath], {
|
||||||
console.error("An error occurred:", err);
|
env: { ...process.env, PORT: CLIENT_PORT },
|
||||||
process.exit(1);
|
signal: abort.signal,
|
||||||
});
|
echoOutput: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure our server/client didn't immediately fail
|
||||||
|
await Promise.any([server, client, delay(2 * 1000)]);
|
||||||
|
const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`;
|
||||||
|
console.log(
|
||||||
|
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} 🚀`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.any([server, client]);
|
||||||
|
} catch (e) {
|
||||||
|
if (!cancelled || process.env.DEBUG) throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.then((_) => process.exit(0))
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -13,6 +13,4 @@ const server = http.createServer((request, response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const port = process.env.PORT || 5173;
|
const port = process.env.PORT || 5173;
|
||||||
server.listen(port, () => {
|
server.listen(port, () => {});
|
||||||
console.log(`MCP inspector client running at http://localhost:${port}`);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/mcp.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>MCP Inspector</title>
|
<title>MCP Inspector</title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-client",
|
"name": "@modelcontextprotocol/inspector-client",
|
||||||
"version": "0.1.0",
|
"version": "0.2.4",
|
||||||
"private": true,
|
|
||||||
"description": "Client-side application for the Model Context Protocol inspector",
|
"description": "Client-side application for the Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
"homepage": "https://modelcontextprotocol.github.io",
|
"homepage": "https://modelcontextprotocol.io",
|
||||||
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -22,7 +21,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "*",
|
"@modelcontextprotocol/sdk": "^1.0.1",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
"@radix-ui/react-select": "^2.1.2",
|
"@radix-ui/react-select": "^2.1.2",
|
||||||
@@ -33,6 +32,7 @@
|
|||||||
"lucide-react": "^0.447.0",
|
"lucide-react": "^0.447.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-toastify": "^10.0.6",
|
||||||
"serve-handler": "^6.1.6",
|
"serve-handler": "^6.1.6",
|
||||||
"tailwind-merge": "^2.5.3",
|
"tailwind-merge": "^2.5.3",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|||||||
12
client/public/mcp.svg
Normal file
12
client/public/mcp.svg
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_19_13)">
|
||||||
|
<path d="M18 84.8528L85.8822 16.9706C95.2548 7.59798 110.451 7.59798 119.823 16.9706V16.9706C129.196 26.3431 129.196 41.5391 119.823 50.9117L68.5581 102.177" stroke="black" stroke-width="12" stroke-linecap="round"/>
|
||||||
|
<path d="M69.2652 101.47L119.823 50.9117C129.196 41.5391 144.392 41.5391 153.765 50.9117L154.118 51.2652C163.491 60.6378 163.491 75.8338 154.118 85.2063L92.7248 146.6C89.6006 149.724 89.6006 154.789 92.7248 157.913L105.331 170.52" stroke="black" stroke-width="12" stroke-linecap="round"/>
|
||||||
|
<path d="M102.853 33.9411L52.6482 84.1457C43.2756 93.5183 43.2756 108.714 52.6482 118.087V118.087C62.0208 127.459 77.2167 127.459 86.5893 118.087L136.794 67.8822" stroke="black" stroke-width="12" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_19_13">
|
||||||
|
<rect width="180" height="180" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 973 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,8 +1,10 @@
|
|||||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||||
import {
|
import {
|
||||||
CompatibilityCallToolResultSchema,
|
ClientNotification,
|
||||||
ClientRequest,
|
ClientRequest,
|
||||||
|
CompatibilityCallToolResult,
|
||||||
|
CompatibilityCallToolResultSchema,
|
||||||
CreateMessageRequestSchema,
|
CreateMessageRequestSchema,
|
||||||
CreateMessageResult,
|
CreateMessageResult,
|
||||||
EmptyResultSchema,
|
EmptyResultSchema,
|
||||||
@@ -14,53 +16,51 @@ import {
|
|||||||
ListToolsResultSchema,
|
ListToolsResultSchema,
|
||||||
ProgressNotificationSchema,
|
ProgressNotificationSchema,
|
||||||
ReadResourceResultSchema,
|
ReadResourceResultSchema,
|
||||||
|
Request,
|
||||||
Resource,
|
Resource,
|
||||||
ResourceTemplate,
|
ResourceTemplate,
|
||||||
|
Result,
|
||||||
Root,
|
Root,
|
||||||
ServerNotification,
|
ServerNotification,
|
||||||
Tool,
|
Tool,
|
||||||
CompatibilityCallToolResult,
|
|
||||||
ClientNotification,
|
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Notification,
|
||||||
SelectContent,
|
StdErrNotification,
|
||||||
SelectItem,
|
StdErrNotificationSchema,
|
||||||
SelectTrigger,
|
} from "./lib/notificationTypes";
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import {
|
import {
|
||||||
Bell,
|
Bell,
|
||||||
Files,
|
Files,
|
||||||
|
FolderTree,
|
||||||
Hammer,
|
Hammer,
|
||||||
Hash,
|
Hash,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
Play,
|
|
||||||
Send,
|
|
||||||
Terminal,
|
|
||||||
FolderTree,
|
|
||||||
ChevronDown,
|
|
||||||
ChevronRight,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
|
import { toast } from "react-toastify";
|
||||||
import { ZodType } from "zod";
|
import { ZodType } from "zod";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import ConsoleTab from "./components/ConsoleTab";
|
import ConsoleTab from "./components/ConsoleTab";
|
||||||
import HistoryAndNotifications from "./components/History";
|
import HistoryAndNotifications from "./components/History";
|
||||||
import PingTab from "./components/PingTab";
|
import PingTab from "./components/PingTab";
|
||||||
import PromptsTab, { Prompt } from "./components/PromptsTab";
|
import PromptsTab, { Prompt } from "./components/PromptsTab";
|
||||||
import RequestsTab from "./components/RequestsTabs";
|
|
||||||
import ResourcesTab from "./components/ResourcesTab";
|
import ResourcesTab from "./components/ResourcesTab";
|
||||||
import RootsTab from "./components/RootsTab";
|
import RootsTab from "./components/RootsTab";
|
||||||
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
|
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
|
||||||
import Sidebar from "./components/Sidebar";
|
import Sidebar from "./components/Sidebar";
|
||||||
import ToolsTab from "./components/ToolsTab";
|
import ToolsTab from "./components/ToolsTab";
|
||||||
|
|
||||||
|
const DEFAULT_REQUEST_TIMEOUT_MSEC = 10000;
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const PROXY_PORT = params.get("proxyPort") ?? "3000";
|
||||||
|
const PROXY_SERVER_URL = `http://localhost:${PROXY_PORT}`;
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [connectionStatus, setConnectionStatus] = useState<
|
const [connectionStatus, setConnectionStatus] = useState<
|
||||||
"disconnected" | "connected" | "error"
|
"disconnected" | "connected" | "error"
|
||||||
@@ -75,29 +75,30 @@ const App = () => {
|
|||||||
const [tools, setTools] = useState<Tool[]>([]);
|
const [tools, setTools] = useState<Tool[]>([]);
|
||||||
const [toolResult, setToolResult] =
|
const [toolResult, setToolResult] =
|
||||||
useState<CompatibilityCallToolResult | null>(null);
|
useState<CompatibilityCallToolResult | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [errors, setErrors] = useState<Record<string, string | null>>({
|
||||||
|
resources: null,
|
||||||
|
prompts: null,
|
||||||
|
tools: null,
|
||||||
|
});
|
||||||
const [command, setCommand] = useState<string>(() => {
|
const [command, setCommand] = useState<string>(() => {
|
||||||
return (
|
return localStorage.getItem("lastCommand") || "mcp-server-everything";
|
||||||
localStorage.getItem("lastCommand") ||
|
|
||||||
"/Users/ashwin/.nvm/versions/node/v18.20.4/bin/node"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
const [args, setArgs] = useState<string>(() => {
|
const [args, setArgs] = useState<string>(() => {
|
||||||
return (
|
return localStorage.getItem("lastArgs") || "";
|
||||||
localStorage.getItem("lastArgs") ||
|
|
||||||
"/Users/ashwin/code/mcp/example-servers/build/everything/stdio.js"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
const [url, setUrl] = useState<string>("http://localhost:3001/sse");
|
|
||||||
|
const [sseUrl, setSseUrl] = useState<string>("http://localhost:3001/sse");
|
||||||
const [transportType, setTransportType] = useState<"stdio" | "sse">("stdio");
|
const [transportType, setTransportType] = useState<"stdio" | "sse">("stdio");
|
||||||
const [requestHistory, setRequestHistory] = useState<
|
const [requestHistory, setRequestHistory] = useState<
|
||||||
{ request: string; response?: string }[]
|
{ request: string; response?: string }[]
|
||||||
>([]);
|
>([]);
|
||||||
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
||||||
const [notifications, setNotifications] = useState<ServerNotification[]>([]);
|
const [notifications, setNotifications] = useState<ServerNotification[]>([]);
|
||||||
|
const [stdErrNotifications, setStdErrNotifications] = useState<
|
||||||
|
StdErrNotification[]
|
||||||
|
>([]);
|
||||||
const [roots, setRoots] = useState<Root[]>([]);
|
const [roots, setRoots] = useState<Root[]>([]);
|
||||||
const [env, setEnv] = useState<Record<string, string>>({});
|
const [env, setEnv] = useState<Record<string, string>>({});
|
||||||
const [showEnvVars, setShowEnvVars] = useState(false);
|
|
||||||
|
|
||||||
const [pendingSampleRequests, setPendingSampleRequests] = useState<
|
const [pendingSampleRequests, setPendingSampleRequests] = useState<
|
||||||
Array<
|
Array<
|
||||||
@@ -142,6 +143,49 @@ const App = () => {
|
|||||||
>();
|
>();
|
||||||
const [nextToolCursor, setNextToolCursor] = useState<string | undefined>();
|
const [nextToolCursor, setNextToolCursor] = useState<string | undefined>();
|
||||||
const progressTokenRef = useRef(0);
|
const progressTokenRef = useRef(0);
|
||||||
|
const [historyPaneHeight, setHistoryPaneHeight] = useState<number>(300);
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
const dragStartY = useRef<number>(0);
|
||||||
|
const dragStartHeight = useRef<number>(0);
|
||||||
|
|
||||||
|
const handleDragStart = useCallback(
|
||||||
|
(e: React.MouseEvent) => {
|
||||||
|
setIsDragging(true);
|
||||||
|
dragStartY.current = e.clientY;
|
||||||
|
dragStartHeight.current = historyPaneHeight;
|
||||||
|
document.body.style.userSelect = "none";
|
||||||
|
},
|
||||||
|
[historyPaneHeight],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDragMove = useCallback(
|
||||||
|
(e: MouseEvent) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
const deltaY = dragStartY.current - e.clientY;
|
||||||
|
const newHeight = Math.max(
|
||||||
|
100,
|
||||||
|
Math.min(800, dragStartHeight.current + deltaY),
|
||||||
|
);
|
||||||
|
setHistoryPaneHeight(newHeight);
|
||||||
|
},
|
||||||
|
[isDragging],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDragEnd = useCallback(() => {
|
||||||
|
setIsDragging(false);
|
||||||
|
document.body.style.userSelect = "";
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDragging) {
|
||||||
|
window.addEventListener("mousemove", handleDragMove);
|
||||||
|
window.addEventListener("mouseup", handleDragEnd);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("mousemove", handleDragMove);
|
||||||
|
window.removeEventListener("mouseup", handleDragEnd);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [isDragging, handleDragMove, handleDragEnd]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem("lastCommand", command);
|
localStorage.setItem("lastCommand", command);
|
||||||
@@ -152,9 +196,17 @@ const App = () => {
|
|||||||
}, [args]);
|
}, [args]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("http://localhost:3000/default-environment")
|
fetch(`${PROXY_SERVER_URL}/config`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => setEnv(data))
|
.then((data) => {
|
||||||
|
setEnv(data.defaultEnvironment);
|
||||||
|
if (data.defaultCommand) {
|
||||||
|
setCommand(data.defaultCommand);
|
||||||
|
}
|
||||||
|
if (data.defaultArgs) {
|
||||||
|
setArgs(data.defaultArgs);
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch((error) =>
|
.catch((error) =>
|
||||||
console.error("Error fetching default environment:", error),
|
console.error("Error fetching default environment:", error),
|
||||||
);
|
);
|
||||||
@@ -174,20 +226,51 @@ const App = () => {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearError = (tabKey: keyof typeof errors) => {
|
||||||
|
setErrors((prev) => ({ ...prev, [tabKey]: null }));
|
||||||
|
};
|
||||||
|
|
||||||
const makeRequest = async <T extends ZodType<object>>(
|
const makeRequest = async <T extends ZodType<object>>(
|
||||||
request: ClientRequest,
|
request: ClientRequest,
|
||||||
schema: T,
|
schema: T,
|
||||||
|
tabKey?: keyof typeof errors,
|
||||||
) => {
|
) => {
|
||||||
if (!mcpClient) {
|
if (!mcpClient) {
|
||||||
throw new Error("MCP client not connected");
|
throw new Error("MCP client not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await mcpClient.request(request, schema);
|
const abortController = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
abortController.abort("Request timed out");
|
||||||
|
}, DEFAULT_REQUEST_TIMEOUT_MSEC);
|
||||||
|
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
response = await mcpClient.request(request, schema, {
|
||||||
|
signal: abortController.signal,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
pushHistory(request, response);
|
pushHistory(request, response);
|
||||||
|
|
||||||
|
if (tabKey !== undefined) {
|
||||||
|
clearError(tabKey);
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
setError((e as Error).message);
|
const errorString = (e as Error).message ?? String(e);
|
||||||
|
if (tabKey === undefined) {
|
||||||
|
toast.error(errorString);
|
||||||
|
} else {
|
||||||
|
setErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[tabKey]: errorString,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -201,7 +284,7 @@ const App = () => {
|
|||||||
await mcpClient.notification(notification);
|
await mcpClient.notification(notification);
|
||||||
pushHistory(notification);
|
pushHistory(notification);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
setError((e as Error).message);
|
toast.error((e as Error).message ?? String(e));
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -213,6 +296,7 @@ const App = () => {
|
|||||||
params: nextResourceCursor ? { cursor: nextResourceCursor } : {},
|
params: nextResourceCursor ? { cursor: nextResourceCursor } : {},
|
||||||
},
|
},
|
||||||
ListResourcesResultSchema,
|
ListResourcesResultSchema,
|
||||||
|
"resources",
|
||||||
);
|
);
|
||||||
setResources(resources.concat(response.resources ?? []));
|
setResources(resources.concat(response.resources ?? []));
|
||||||
setNextResourceCursor(response.nextCursor);
|
setNextResourceCursor(response.nextCursor);
|
||||||
@@ -227,6 +311,7 @@ const App = () => {
|
|||||||
: {},
|
: {},
|
||||||
},
|
},
|
||||||
ListResourceTemplatesResultSchema,
|
ListResourceTemplatesResultSchema,
|
||||||
|
"resources",
|
||||||
);
|
);
|
||||||
setResourceTemplates(
|
setResourceTemplates(
|
||||||
resourceTemplates.concat(response.resourceTemplates ?? []),
|
resourceTemplates.concat(response.resourceTemplates ?? []),
|
||||||
@@ -241,6 +326,7 @@ const App = () => {
|
|||||||
params: { uri },
|
params: { uri },
|
||||||
},
|
},
|
||||||
ReadResourceResultSchema,
|
ReadResourceResultSchema,
|
||||||
|
"resources",
|
||||||
);
|
);
|
||||||
setResourceContent(JSON.stringify(response, null, 2));
|
setResourceContent(JSON.stringify(response, null, 2));
|
||||||
};
|
};
|
||||||
@@ -252,6 +338,7 @@ const App = () => {
|
|||||||
params: nextPromptCursor ? { cursor: nextPromptCursor } : {},
|
params: nextPromptCursor ? { cursor: nextPromptCursor } : {},
|
||||||
},
|
},
|
||||||
ListPromptsResultSchema,
|
ListPromptsResultSchema,
|
||||||
|
"prompts",
|
||||||
);
|
);
|
||||||
setPrompts(response.prompts);
|
setPrompts(response.prompts);
|
||||||
setNextPromptCursor(response.nextCursor);
|
setNextPromptCursor(response.nextCursor);
|
||||||
@@ -264,6 +351,7 @@ const App = () => {
|
|||||||
params: { name, arguments: args },
|
params: { name, arguments: args },
|
||||||
},
|
},
|
||||||
GetPromptResultSchema,
|
GetPromptResultSchema,
|
||||||
|
"prompts",
|
||||||
);
|
);
|
||||||
setPromptContent(JSON.stringify(response, null, 2));
|
setPromptContent(JSON.stringify(response, null, 2));
|
||||||
};
|
};
|
||||||
@@ -275,6 +363,7 @@ const App = () => {
|
|||||||
params: nextToolCursor ? { cursor: nextToolCursor } : {},
|
params: nextToolCursor ? { cursor: nextToolCursor } : {},
|
||||||
},
|
},
|
||||||
ListToolsResultSchema,
|
ListToolsResultSchema,
|
||||||
|
"tools",
|
||||||
);
|
);
|
||||||
setTools(response.tools);
|
setTools(response.tools);
|
||||||
setNextToolCursor(response.nextCursor);
|
setNextToolCursor(response.nextCursor);
|
||||||
@@ -293,6 +382,7 @@ const App = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
CompatibilityCallToolResultSchema,
|
CompatibilityCallToolResultSchema,
|
||||||
|
"tools",
|
||||||
);
|
);
|
||||||
setToolResult(response);
|
setToolResult(response);
|
||||||
};
|
};
|
||||||
@@ -303,12 +393,23 @@ const App = () => {
|
|||||||
|
|
||||||
const connectMcpServer = async () => {
|
const connectMcpServer = async () => {
|
||||||
try {
|
try {
|
||||||
const client = new Client({
|
const client = new Client<Request, Notification, Result>(
|
||||||
name: "mcp-inspector",
|
{
|
||||||
version: "0.0.1",
|
name: "mcp-inspector",
|
||||||
});
|
version: "0.0.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: {
|
||||||
|
// Support all client capabilities since we're an inspector tool
|
||||||
|
sampling: {},
|
||||||
|
roots: {
|
||||||
|
listChanged: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const backendUrl = new URL("http://localhost:3000/sse");
|
const backendUrl = new URL(`${PROXY_SERVER_URL}/sse`);
|
||||||
|
|
||||||
backendUrl.searchParams.append("transportType", transportType);
|
backendUrl.searchParams.append("transportType", transportType);
|
||||||
if (transportType === "stdio") {
|
if (transportType === "stdio") {
|
||||||
@@ -316,12 +417,10 @@ const App = () => {
|
|||||||
backendUrl.searchParams.append("args", args);
|
backendUrl.searchParams.append("args", args);
|
||||||
backendUrl.searchParams.append("env", JSON.stringify(env));
|
backendUrl.searchParams.append("env", JSON.stringify(env));
|
||||||
} else {
|
} else {
|
||||||
backendUrl.searchParams.append("url", url);
|
backendUrl.searchParams.append("url", sseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientTransport = new SSEClientTransport(backendUrl);
|
const clientTransport = new SSEClientTransport(backendUrl);
|
||||||
await client.connect(clientTransport);
|
|
||||||
|
|
||||||
client.setNotificationHandler(
|
client.setNotificationHandler(
|
||||||
ProgressNotificationSchema,
|
ProgressNotificationSchema,
|
||||||
(notification) => {
|
(notification) => {
|
||||||
@@ -332,6 +431,18 @@ const App = () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
client.setNotificationHandler(
|
||||||
|
StdErrNotificationSchema,
|
||||||
|
(notification) => {
|
||||||
|
setStdErrNotifications((prevErrorNotifications) => [
|
||||||
|
...prevErrorNotifications,
|
||||||
|
notification,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await client.connect(clientTransport);
|
||||||
|
|
||||||
client.setRequestHandler(CreateMessageRequestSchema, (request) => {
|
client.setRequestHandler(CreateMessageRequestSchema, (request) => {
|
||||||
return new Promise<CreateMessageResult>((resolve, reject) => {
|
return new Promise<CreateMessageResult>((resolve, reject) => {
|
||||||
setPendingSampleRequests((prev) => [
|
setPendingSampleRequests((prev) => [
|
||||||
@@ -354,232 +465,174 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen bg-gray-100">
|
<div className="flex h-screen bg-background">
|
||||||
<Sidebar connectionStatus={connectionStatus} />
|
<Sidebar
|
||||||
|
connectionStatus={connectionStatus}
|
||||||
|
transportType={transportType}
|
||||||
|
setTransportType={setTransportType}
|
||||||
|
command={command}
|
||||||
|
setCommand={setCommand}
|
||||||
|
args={args}
|
||||||
|
setArgs={setArgs}
|
||||||
|
sseUrl={sseUrl}
|
||||||
|
setSseUrl={setSseUrl}
|
||||||
|
env={env}
|
||||||
|
setEnv={setEnv}
|
||||||
|
onConnect={connectMcpServer}
|
||||||
|
stdErrNotifications={stdErrNotifications}
|
||||||
|
/>
|
||||||
<div className="flex-1 flex flex-col overflow-hidden">
|
<div className="flex-1 flex flex-col overflow-hidden">
|
||||||
<h1 className="text-2xl font-bold p-4">MCP Inspector</h1>
|
<div className="flex-1 overflow-auto">
|
||||||
<div className="flex-1 overflow-auto flex">
|
{mcpClient ? (
|
||||||
<div className="flex-1">
|
<Tabs defaultValue="resources" className="w-full p-4">
|
||||||
<div className="p-4 bg-white shadow-md m-4 rounded-md">
|
<TabsList className="mb-4 p-0">
|
||||||
<h2 className="text-lg font-semibold mb-2">Connect MCP Server</h2>
|
<TabsTrigger value="resources">
|
||||||
<div className="flex space-x-2 mb-2">
|
<Files className="w-4 h-4 mr-2" />
|
||||||
<Select
|
Resources
|
||||||
value={transportType}
|
</TabsTrigger>
|
||||||
onValueChange={(value: "stdio" | "sse") =>
|
<TabsTrigger value="prompts">
|
||||||
setTransportType(value)
|
<MessageSquare className="w-4 h-4 mr-2" />
|
||||||
}
|
Prompts
|
||||||
>
|
</TabsTrigger>
|
||||||
<SelectTrigger className="w-[180px]">
|
<TabsTrigger value="tools">
|
||||||
<SelectValue placeholder="Select transport type" />
|
<Hammer className="w-4 h-4 mr-2" />
|
||||||
</SelectTrigger>
|
Tools
|
||||||
<SelectContent>
|
</TabsTrigger>
|
||||||
<SelectItem value="stdio">STDIO</SelectItem>
|
<TabsTrigger value="ping">
|
||||||
<SelectItem value="sse">SSE</SelectItem>
|
<Bell className="w-4 h-4 mr-2" />
|
||||||
</SelectContent>
|
Ping
|
||||||
</Select>
|
</TabsTrigger>
|
||||||
{transportType === "stdio" ? (
|
<TabsTrigger value="sampling" className="relative">
|
||||||
<>
|
<Hash className="w-4 h-4 mr-2" />
|
||||||
<Input
|
Sampling
|
||||||
placeholder="Command"
|
{pendingSampleRequests.length > 0 && (
|
||||||
value={command}
|
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
|
||||||
onChange={(e) => setCommand(e.target.value)}
|
{pendingSampleRequests.length}
|
||||||
/>
|
</span>
|
||||||
<Input
|
|
||||||
placeholder="Arguments (space-separated)"
|
|
||||||
value={args}
|
|
||||||
onChange={(e) => setArgs(e.target.value)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Input
|
|
||||||
placeholder="URL"
|
|
||||||
value={url}
|
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button onClick={connectMcpServer}>
|
|
||||||
<Play className="w-4 h-4 mr-2" />
|
|
||||||
Connect
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{transportType === "stdio" && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setShowEnvVars(!showEnvVars)}
|
|
||||||
className="flex items-center"
|
|
||||||
>
|
|
||||||
{showEnvVars ? (
|
|
||||||
<ChevronDown className="w-4 h-4 mr-2" />
|
|
||||||
) : (
|
|
||||||
<ChevronRight className="w-4 h-4 mr-2" />
|
|
||||||
)}
|
|
||||||
Environment Variables
|
|
||||||
</Button>
|
|
||||||
{showEnvVars && (
|
|
||||||
<div className="mt-2">
|
|
||||||
{Object.entries(env).map(([key, value]) => (
|
|
||||||
<div key={key} className="flex space-x-2 mb-2">
|
|
||||||
<Input
|
|
||||||
placeholder="Key"
|
|
||||||
value={key}
|
|
||||||
onChange={(e) =>
|
|
||||||
setEnv((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[e.target.value]: value,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
placeholder="Value"
|
|
||||||
value={value}
|
|
||||||
onChange={(e) =>
|
|
||||||
setEnv((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[key]: e.target.value,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
setEnv((prev) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { [key]: _, ...rest } = prev;
|
|
||||||
return rest;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
onClick={() => setEnv((prev) => ({ ...prev, "": "" }))}
|
|
||||||
>
|
|
||||||
Add Environment Variable
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</TabsTrigger>
|
||||||
)}
|
<TabsTrigger value="roots">
|
||||||
</div>
|
<FolderTree className="w-4 h-4 mr-2" />
|
||||||
{mcpClient ? (
|
Roots
|
||||||
<Tabs defaultValue="resources" className="w-full p-4">
|
</TabsTrigger>
|
||||||
<TabsList className="mb-4 p-0">
|
</TabsList>
|
||||||
<TabsTrigger value="resources">
|
|
||||||
<Files className="w-4 h-4 mr-2" />
|
|
||||||
Resources
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="prompts">
|
|
||||||
<MessageSquare className="w-4 h-4 mr-2" />
|
|
||||||
Prompts
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="requests" disabled>
|
|
||||||
<Send className="w-4 h-4 mr-2" />
|
|
||||||
Requests
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="tools">
|
|
||||||
<Hammer className="w-4 h-4 mr-2" />
|
|
||||||
Tools
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="console" disabled>
|
|
||||||
<Terminal className="w-4 h-4 mr-2" />
|
|
||||||
Console
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="ping">
|
|
||||||
<Bell className="w-4 h-4 mr-2" />
|
|
||||||
Ping
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="sampling" className="relative">
|
|
||||||
<Hash className="w-4 h-4 mr-2" />
|
|
||||||
Sampling
|
|
||||||
{pendingSampleRequests.length > 0 && (
|
|
||||||
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
|
|
||||||
{pendingSampleRequests.length}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="roots">
|
|
||||||
<FolderTree className="w-4 h-4 mr-2" />
|
|
||||||
Roots
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<ResourcesTab
|
<ResourcesTab
|
||||||
resources={resources}
|
resources={resources}
|
||||||
resourceTemplates={resourceTemplates}
|
resourceTemplates={resourceTemplates}
|
||||||
listResources={listResources}
|
listResources={() => {
|
||||||
listResourceTemplates={listResourceTemplates}
|
clearError("resources");
|
||||||
readResource={readResource}
|
listResources();
|
||||||
selectedResource={selectedResource}
|
}}
|
||||||
setSelectedResource={setSelectedResource}
|
listResourceTemplates={() => {
|
||||||
resourceContent={resourceContent}
|
clearError("resources");
|
||||||
nextCursor={nextResourceCursor}
|
listResourceTemplates();
|
||||||
nextTemplateCursor={nextResourceTemplateCursor}
|
}}
|
||||||
error={error}
|
readResource={(uri) => {
|
||||||
/>
|
clearError("resources");
|
||||||
<PromptsTab
|
readResource(uri);
|
||||||
prompts={prompts}
|
}}
|
||||||
listPrompts={listPrompts}
|
selectedResource={selectedResource}
|
||||||
getPrompt={getPrompt}
|
setSelectedResource={(resource) => {
|
||||||
selectedPrompt={selectedPrompt}
|
clearError("resources");
|
||||||
setSelectedPrompt={setSelectedPrompt}
|
setSelectedResource(resource);
|
||||||
promptContent={promptContent}
|
}}
|
||||||
nextCursor={nextPromptCursor}
|
resourceContent={resourceContent}
|
||||||
error={error}
|
nextCursor={nextResourceCursor}
|
||||||
/>
|
nextTemplateCursor={nextResourceTemplateCursor}
|
||||||
<RequestsTab />
|
error={errors.resources}
|
||||||
<ToolsTab
|
/>
|
||||||
tools={tools}
|
<PromptsTab
|
||||||
listTools={listTools}
|
prompts={prompts}
|
||||||
callTool={callTool}
|
listPrompts={() => {
|
||||||
selectedTool={selectedTool}
|
clearError("prompts");
|
||||||
setSelectedTool={(tool) => {
|
listPrompts();
|
||||||
setSelectedTool(tool);
|
}}
|
||||||
setToolResult(null);
|
getPrompt={(name, args) => {
|
||||||
}}
|
clearError("prompts");
|
||||||
toolResult={toolResult}
|
getPrompt(name, args);
|
||||||
nextCursor={nextToolCursor}
|
}}
|
||||||
error={error}
|
selectedPrompt={selectedPrompt}
|
||||||
/>
|
setSelectedPrompt={(prompt) => {
|
||||||
<ConsoleTab />
|
clearError("prompts");
|
||||||
<PingTab
|
setSelectedPrompt(prompt);
|
||||||
onPingClick={() => {
|
}}
|
||||||
void makeRequest(
|
promptContent={promptContent}
|
||||||
{
|
nextCursor={nextPromptCursor}
|
||||||
method: "ping" as const,
|
error={errors.prompts}
|
||||||
},
|
/>
|
||||||
EmptyResultSchema,
|
<ToolsTab
|
||||||
);
|
tools={tools}
|
||||||
}}
|
listTools={() => {
|
||||||
/>
|
clearError("tools");
|
||||||
<SamplingTab
|
listTools();
|
||||||
pendingRequests={pendingSampleRequests}
|
}}
|
||||||
onApprove={handleApproveSampling}
|
callTool={(name, params) => {
|
||||||
onReject={handleRejectSampling}
|
clearError("tools");
|
||||||
/>
|
callTool(name, params);
|
||||||
<RootsTab
|
}}
|
||||||
roots={roots}
|
selectedTool={selectedTool}
|
||||||
setRoots={setRoots}
|
setSelectedTool={(tool) => {
|
||||||
onRootsChange={handleRootsChange}
|
clearError("tools");
|
||||||
/>
|
setSelectedTool(tool);
|
||||||
</div>
|
setToolResult(null);
|
||||||
</Tabs>
|
}}
|
||||||
) : (
|
toolResult={toolResult}
|
||||||
<div className="flex items-center justify-center h-full">
|
nextCursor={nextToolCursor}
|
||||||
<p className="text-lg text-gray-500">
|
error={errors.tools}
|
||||||
Connect to an MCP server to start inspecting
|
/>
|
||||||
</p>
|
<ConsoleTab />
|
||||||
|
<PingTab
|
||||||
|
onPingClick={() => {
|
||||||
|
void makeRequest(
|
||||||
|
{
|
||||||
|
method: "ping" as const,
|
||||||
|
},
|
||||||
|
EmptyResultSchema,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SamplingTab
|
||||||
|
pendingRequests={pendingSampleRequests}
|
||||||
|
onApprove={handleApproveSampling}
|
||||||
|
onReject={handleRejectSampling}
|
||||||
|
/>
|
||||||
|
<RootsTab
|
||||||
|
roots={roots}
|
||||||
|
setRoots={setRoots}
|
||||||
|
onRootsChange={handleRootsChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</Tabs>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<p className="text-lg text-gray-500">
|
||||||
|
Connect to an MCP server to start inspecting
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="relative border-t border-border"
|
||||||
|
style={{
|
||||||
|
height: `${historyPaneHeight}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute w-full h-4 -top-2 cursor-row-resize flex items-center justify-center hover:bg-accent/50"
|
||||||
|
onMouseDown={handleDragStart}
|
||||||
|
>
|
||||||
|
<div className="w-8 h-1 rounded-full bg-border" />
|
||||||
|
</div>
|
||||||
|
<div className="h-full overflow-auto">
|
||||||
|
<HistoryAndNotifications
|
||||||
|
requestHistory={requestHistory}
|
||||||
|
serverNotifications={notifications}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HistoryAndNotifications
|
|
||||||
requestHistory={requestHistory}
|
|
||||||
serverNotifications={notifications}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -29,8 +29,8 @@ const HistoryAndNotifications = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-64 bg-white shadow-md p-4 overflow-hidden flex flex-col h-full">
|
<div className="bg-card overflow-hidden flex h-full">
|
||||||
<div className="flex-1 overflow-y-auto mb-4 border-b pb-4">
|
<div className="flex-1 overflow-y-auto p-4 border-r">
|
||||||
<h2 className="text-lg font-semibold mb-4">History</h2>
|
<h2 className="text-lg font-semibold mb-4">History</h2>
|
||||||
{requestHistory.length === 0 ? (
|
{requestHistory.length === 0 ? (
|
||||||
<p className="text-sm text-gray-500 italic">No history yet</p>
|
<p className="text-sm text-gray-500 italic">No history yet</p>
|
||||||
@@ -42,7 +42,7 @@ const HistoryAndNotifications = ({
|
|||||||
.map((request, index) => (
|
.map((request, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className="text-sm text-gray-600 bg-gray-100 p-2 rounded"
|
className="text-sm text-foreground bg-secondary p-2 rounded"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex justify-between items-center cursor-pointer"
|
className="flex justify-between items-center cursor-pointer"
|
||||||
@@ -74,7 +74,7 @@ const HistoryAndNotifications = ({
|
|||||||
<Copy size={16} />
|
<Copy size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<pre className="whitespace-pre-wrap break-words bg-blue-50 p-2 rounded">
|
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
|
||||||
{JSON.stringify(JSON.parse(request.request), null, 2)}
|
{JSON.stringify(JSON.parse(request.request), null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,7 +91,7 @@ const HistoryAndNotifications = ({
|
|||||||
<Copy size={16} />
|
<Copy size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<pre className="whitespace-pre-wrap break-words bg-green-50 p-2 rounded">
|
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
|
||||||
{JSON.stringify(
|
{JSON.stringify(
|
||||||
JSON.parse(request.response),
|
JSON.parse(request.response),
|
||||||
null,
|
null,
|
||||||
@@ -107,7 +107,7 @@ const HistoryAndNotifications = ({
|
|||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto p-4">
|
||||||
<h2 className="text-lg font-semibold mb-4">Server Notifications</h2>
|
<h2 className="text-lg font-semibold mb-4">Server Notifications</h2>
|
||||||
{serverNotifications.length === 0 ? (
|
{serverNotifications.length === 0 ? (
|
||||||
<p className="text-sm text-gray-500 italic">No notifications yet</p>
|
<p className="text-sm text-gray-500 italic">No notifications yet</p>
|
||||||
@@ -119,7 +119,7 @@ const HistoryAndNotifications = ({
|
|||||||
.map((notification, index) => (
|
.map((notification, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className="text-sm text-gray-600 bg-gray-100 p-2 rounded"
|
className="text-sm text-foreground bg-secondary p-2 rounded"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex justify-between items-center cursor-pointer"
|
className="flex justify-between items-center cursor-pointer"
|
||||||
@@ -146,7 +146,7 @@ const HistoryAndNotifications = ({
|
|||||||
<Copy size={16} />
|
<Copy size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<pre className="whitespace-pre-wrap break-words bg-purple-50 p-2 rounded">
|
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
|
||||||
{JSON.stringify(notification, null, 2)}
|
{JSON.stringify(notification, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ const ListPane = <T extends object>({
|
|||||||
buttonText,
|
buttonText,
|
||||||
isButtonDisabled,
|
isButtonDisabled,
|
||||||
}: ListPaneProps<T>) => (
|
}: ListPaneProps<T>) => (
|
||||||
<div className="bg-white rounded-lg shadow">
|
<div className="bg-card rounded-lg shadow">
|
||||||
<div className="p-4 border-b border-gray-200">
|
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<h3 className="font-semibold">{title}</h3>
|
<h3 className="font-semibold dark:text-white">{title}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Button
|
<Button
|
||||||
@@ -36,7 +36,7 @@ const ListPane = <T extends object>({
|
|||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center p-2 rounded hover:bg-gray-50 cursor-pointer"
|
className="flex items-center p-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer"
|
||||||
onClick={() => setSelectedItem(item)}
|
onClick={() => setSelectedItem(item)}
|
||||||
>
|
>
|
||||||
{renderItem(item)}
|
{renderItem(item)}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ const PromptsTab = ({
|
|||||||
isButtonDisabled={!nextCursor && prompts.length > 0}
|
isButtonDisabled={!nextCursor && prompts.length > 0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow">
|
<div className="bg-card rounded-lg shadow">
|
||||||
<div className="p-4 border-b border-gray-200">
|
<div className="p-4 border-b border-gray-200">
|
||||||
<h3 className="font-semibold">
|
<h3 className="font-semibold">
|
||||||
{selectedPrompt ? selectedPrompt.name : "Select a prompt"}
|
{selectedPrompt ? selectedPrompt.name : "Select a prompt"}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import { TabsContent } from "@/components/ui/tabs";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import { Send } from "lucide-react";
|
|
||||||
|
|
||||||
const RequestsTab = () => (
|
|
||||||
<TabsContent value="requests" className="space-y-4">
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<Input placeholder="Method name" />
|
|
||||||
<Button>
|
|
||||||
<Send className="w-4 h-4 mr-2" />
|
|
||||||
Send
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Textarea
|
|
||||||
placeholder="Request parameters (JSON)"
|
|
||||||
className="h-64 font-mono"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="bg-gray-50 p-4 rounded-lg h-96 font-mono text-sm overflow-auto">
|
|
||||||
<div className="text-gray-500 mb-2">Response:</div>
|
|
||||||
{/* Response content would go here */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default RequestsTab;
|
|
||||||
@@ -111,7 +111,7 @@ const ResourcesTab = ({
|
|||||||
isButtonDisabled={!nextTemplateCursor && resourceTemplates.length > 0}
|
isButtonDisabled={!nextTemplateCursor && resourceTemplates.length > 0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow">
|
<div className="bg-card rounded-lg shadow">
|
||||||
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
|
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
|
||||||
<h3
|
<h3
|
||||||
className="font-semibold truncate"
|
className="font-semibold truncate"
|
||||||
@@ -142,7 +142,7 @@ const ResourcesTab = ({
|
|||||||
<AlertDescription>{error}</AlertDescription>
|
<AlertDescription>{error}</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
) : selectedResource ? (
|
) : selectedResource ? (
|
||||||
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-96 whitespace-pre-wrap break-words">
|
<pre className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 whitespace-pre-wrap break-words text-gray-900 dark:text-gray-100">
|
||||||
{resourceContent}
|
{resourceContent}
|
||||||
</pre>
|
</pre>
|
||||||
) : selectedTemplate ? (
|
) : selectedTemplate ? (
|
||||||
|
|||||||
@@ -1,39 +1,245 @@
|
|||||||
import { Menu, Settings } from "lucide-react";
|
import { useState } from "react";
|
||||||
|
import { Play, ChevronDown, ChevronRight } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { StdErrNotification } from "@/lib/notificationTypes";
|
||||||
|
|
||||||
const Sidebar = ({ connectionStatus }: { connectionStatus: string }) => (
|
import useTheme from "../lib/useTheme";
|
||||||
<div className="w-64 bg-white border-r border-gray-200">
|
import { version } from "../../../package.json";
|
||||||
<div className="flex items-center p-4 border-b border-gray-200">
|
|
||||||
<Menu className="w-6 h-6 text-gray-500" />
|
|
||||||
<h1 className="ml-2 text-lg font-semibold">MCP Inspector</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4">
|
interface SidebarProps {
|
||||||
<div className="flex items-center space-x-2 mb-4">
|
connectionStatus: "disconnected" | "connected" | "error";
|
||||||
<div
|
transportType: "stdio" | "sse";
|
||||||
className={`w-2 h-2 rounded-full ${
|
setTransportType: (type: "stdio" | "sse") => void;
|
||||||
connectionStatus === "connected"
|
command: string;
|
||||||
? "bg-green-500"
|
setCommand: (command: string) => void;
|
||||||
: connectionStatus === "error"
|
args: string;
|
||||||
? "bg-red-500"
|
setArgs: (args: string) => void;
|
||||||
: "bg-gray-500"
|
sseUrl: string;
|
||||||
}`}
|
setSseUrl: (url: string) => void;
|
||||||
/>
|
env: Record<string, string>;
|
||||||
<span className="text-sm text-gray-600">
|
setEnv: (env: Record<string, string>) => void;
|
||||||
{connectionStatus === "connected"
|
onConnect: () => void;
|
||||||
? "Connected"
|
stdErrNotifications: StdErrNotification[];
|
||||||
: connectionStatus === "error"
|
}
|
||||||
? "Connection Error"
|
|
||||||
: "Disconnected"}
|
const Sidebar = ({
|
||||||
</span>
|
connectionStatus,
|
||||||
|
transportType,
|
||||||
|
setTransportType,
|
||||||
|
command,
|
||||||
|
setCommand,
|
||||||
|
args,
|
||||||
|
setArgs,
|
||||||
|
sseUrl,
|
||||||
|
setSseUrl,
|
||||||
|
env,
|
||||||
|
setEnv,
|
||||||
|
onConnect,
|
||||||
|
stdErrNotifications,
|
||||||
|
}: SidebarProps) => {
|
||||||
|
const [theme, setTheme] = useTheme();
|
||||||
|
const [showEnvVars, setShowEnvVars] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
|
||||||
|
<div className="flex items-center justify-between p-4 border-b border-gray-200">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<h1 className="ml-2 text-lg font-semibold">
|
||||||
|
MCP Inspector v{version}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button variant="outline" className="w-full justify-start">
|
<div className="p-4 flex-1 overflow-auto">
|
||||||
<Settings className="w-4 h-4 mr-2" />
|
<div className="space-y-4">
|
||||||
Connection Settings
|
<div className="space-y-2">
|
||||||
</Button>
|
<label className="text-sm font-medium">Transport Type</label>
|
||||||
|
<Select
|
||||||
|
value={transportType}
|
||||||
|
onValueChange={(value: "stdio" | "sse") =>
|
||||||
|
setTransportType(value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select transport type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="stdio">STDIO</SelectItem>
|
||||||
|
<SelectItem value="sse">SSE</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{transportType === "stdio" ? (
|
||||||
|
<>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">Command</label>
|
||||||
|
<Input
|
||||||
|
placeholder="Command"
|
||||||
|
value={command}
|
||||||
|
onChange={(e) => setCommand(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">Arguments</label>
|
||||||
|
<Input
|
||||||
|
placeholder="Arguments (space-separated)"
|
||||||
|
value={args}
|
||||||
|
onChange={(e) => setArgs(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">URL</label>
|
||||||
|
<Input
|
||||||
|
placeholder="URL"
|
||||||
|
value={sseUrl}
|
||||||
|
onChange={(e) => setSseUrl(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{transportType === "stdio" && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setShowEnvVars(!showEnvVars)}
|
||||||
|
className="flex items-center w-full"
|
||||||
|
>
|
||||||
|
{showEnvVars ? (
|
||||||
|
<ChevronDown className="w-4 h-4 mr-2" />
|
||||||
|
) : (
|
||||||
|
<ChevronRight className="w-4 h-4 mr-2" />
|
||||||
|
)}
|
||||||
|
Environment Variables
|
||||||
|
</Button>
|
||||||
|
{showEnvVars && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{Object.entries(env).map(([key, value], idx) => (
|
||||||
|
<div key={idx} className="grid grid-cols-[1fr,auto] gap-2">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Input
|
||||||
|
placeholder="Key"
|
||||||
|
value={key}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newEnv = { ...env };
|
||||||
|
delete newEnv[key];
|
||||||
|
newEnv[e.target.value] = value;
|
||||||
|
setEnv(newEnv);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="Value"
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newEnv = { ...env };
|
||||||
|
newEnv[key] = e.target.value;
|
||||||
|
setEnv(newEnv);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { [key]: removed, ...rest } = env;
|
||||||
|
setEnv(rest);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
const newEnv = { ...env };
|
||||||
|
newEnv[""] = "";
|
||||||
|
setEnv(newEnv);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Environment Variable
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Button className="w-full" onClick={onConnect}>
|
||||||
|
<Play className="w-4 h-4 mr-2" />
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-center space-x-2 mb-4">
|
||||||
|
<div
|
||||||
|
className={`w-2 h-2 rounded-full ${
|
||||||
|
connectionStatus === "connected"
|
||||||
|
? "bg-green-500"
|
||||||
|
: connectionStatus === "error"
|
||||||
|
? "bg-red-500"
|
||||||
|
: "bg-gray-500"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-gray-600">
|
||||||
|
{connectionStatus === "connected"
|
||||||
|
? "Connected"
|
||||||
|
: connectionStatus === "error"
|
||||||
|
? "Connection Error"
|
||||||
|
: "Disconnected"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{stdErrNotifications.length > 0 && (
|
||||||
|
<>
|
||||||
|
<div className="mt-4 border-t border-gray-200 pt-4">
|
||||||
|
<h3 className="text-sm font-medium">
|
||||||
|
Error output from MCP server
|
||||||
|
</h3>
|
||||||
|
<div className="mt-2 max-h-80 overflow-y-auto">
|
||||||
|
{stdErrNotifications.map((notification, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="text-sm text-red-500 font-mono py-2 border-b border-gray-200 last:border-b-0"
|
||||||
|
>
|
||||||
|
{notification.params.content}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 border-t">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Select
|
||||||
|
value={theme}
|
||||||
|
onValueChange={(value: string) =>
|
||||||
|
setTheme(value as "system" | "light" | "dark")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[120px]" id="theme-select">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="system">System</SelectItem>
|
||||||
|
<SelectItem value="light">Light</SelectItem>
|
||||||
|
<SelectItem value="dark">Dark</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { TabsContent } from "@/components/ui/tabs";
|
import { TabsContent } from "@/components/ui/tabs";
|
||||||
import { CallToolResult, ListToolsResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import {
|
||||||
|
ListToolsResult,
|
||||||
|
Tool,
|
||||||
|
CallToolResultSchema,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { AlertCircle, Send } from "lucide-react";
|
import { AlertCircle, Send } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import ListPane from "./ListPane";
|
import ListPane from "./ListPane";
|
||||||
@@ -35,17 +40,38 @@ const ToolsTab = ({
|
|||||||
if (!toolResult) return null;
|
if (!toolResult) return null;
|
||||||
|
|
||||||
if ("content" in toolResult) {
|
if ("content" in toolResult) {
|
||||||
const structuredResult = toolResult as CallToolResult;
|
const parsedResult = CallToolResultSchema.safeParse(toolResult);
|
||||||
|
if (!parsedResult.success) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4 className="font-semibold mb-2">Invalid Tool Result:</h4>
|
||||||
|
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64">
|
||||||
|
{JSON.stringify(toolResult, null, 2)}
|
||||||
|
</pre>
|
||||||
|
<h4 className="font-semibold mb-2">Errors:</h4>
|
||||||
|
{parsedResult.error.errors.map((error, idx) => (
|
||||||
|
<pre
|
||||||
|
key={idx}
|
||||||
|
className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64"
|
||||||
|
>
|
||||||
|
{JSON.stringify(error, null, 2)}
|
||||||
|
</pre>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const structuredResult = parsedResult.data;
|
||||||
|
const isError = structuredResult.isError ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h4 className="font-semibold mb-2">
|
<h4 className="font-semibold mb-2">
|
||||||
Tool Result: {structuredResult.isError ? "Error" : "Success"}
|
Tool Result: {isError ? "Error" : "Success"}
|
||||||
</h4>
|
</h4>
|
||||||
{structuredResult.content.map((item, index) => (
|
{structuredResult.content.map((item, index) => (
|
||||||
<div key={index} className="mb-2">
|
<div key={index} className="mb-2">
|
||||||
{item.type === "text" && (
|
{item.type === "text" && (
|
||||||
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-64">
|
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64">
|
||||||
{item.text}
|
{item.text}
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
@@ -57,7 +83,7 @@ const ToolsTab = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.type === "resource" && (
|
{item.type === "resource" && (
|
||||||
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-64">
|
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 whitespace-pre-wrap break-words p-4 rounded text-sm overflow-auto max-h-64">
|
||||||
{JSON.stringify(item.resource, null, 2)}
|
{JSON.stringify(item.resource, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
@@ -96,7 +122,7 @@ const ToolsTab = ({
|
|||||||
isButtonDisabled={!nextCursor && tools.length > 0}
|
isButtonDisabled={!nextCursor && tools.length > 0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow">
|
<div className="bg-card rounded-lg shadow">
|
||||||
<div className="p-4 border-b border-gray-200">
|
<div className="p-4 border-b border-gray-200">
|
||||||
<h3 className="font-semibold">
|
<h3 className="font-semibold">
|
||||||
{selectedTool ? selectedTool.name : "Select a tool"}
|
{selectedTool ? selectedTool.name : "Select a tool"}
|
||||||
@@ -123,24 +149,44 @@ const ToolsTab = ({
|
|||||||
>
|
>
|
||||||
{key}
|
{key}
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
{
|
||||||
// @ts-expect-error value type is currently unknown
|
/* @ts-expect-error value type is currently unknown */
|
||||||
type={value.type === "number" ? "number" : "text"}
|
value.type === "string" ? (
|
||||||
id={key}
|
<Textarea
|
||||||
name={key}
|
id={key}
|
||||||
// @ts-expect-error value type is currently unknown
|
name={key}
|
||||||
placeholder={value.description}
|
// @ts-expect-error value type is currently unknown
|
||||||
onChange={(e) =>
|
placeholder={value.description}
|
||||||
setParams({
|
onChange={(e) =>
|
||||||
...params,
|
setParams({
|
||||||
[key]:
|
...params,
|
||||||
// @ts-expect-error value type is currently unknown
|
[key]: e.target.value,
|
||||||
value.type === "number"
|
})
|
||||||
? Number(e.target.value)
|
}
|
||||||
: e.target.value,
|
className="mt-1"
|
||||||
})
|
/>
|
||||||
}
|
) : (
|
||||||
/>
|
<Input
|
||||||
|
// @ts-expect-error value type is currently unknown
|
||||||
|
type={value.type === "number" ? "number" : "text"}
|
||||||
|
id={key}
|
||||||
|
name={key}
|
||||||
|
// @ts-expect-error value type is currently unknown
|
||||||
|
placeholder={value.description}
|
||||||
|
onChange={(e) =>
|
||||||
|
setParams({
|
||||||
|
...params,
|
||||||
|
[key]:
|
||||||
|
// @ts-expect-error value type is currently unknown
|
||||||
|
value.type === "number"
|
||||||
|
? Number(e.target.value)
|
||||||
|
: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const buttonVariants = cva(
|
|||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
"bg-primary text-primary-foreground shadow hover:bg-primary/90 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700",
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||||
outline:
|
outline:
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
|
|||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-muted data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
19
client/src/lib/notificationTypes.ts
Normal file
19
client/src/lib/notificationTypes.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
NotificationSchema as BaseNotificationSchema,
|
||||||
|
ClientNotificationSchema,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const StdErrNotificationSchema = BaseNotificationSchema.extend({
|
||||||
|
method: z.literal("notifications/stderr"),
|
||||||
|
params: z.object({
|
||||||
|
content: z.string(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NotificationSchema = ClientNotificationSchema.or(
|
||||||
|
StdErrNotificationSchema,
|
||||||
|
);
|
||||||
|
|
||||||
|
export type StdErrNotification = z.infer<typeof StdErrNotificationSchema>;
|
||||||
|
export type Notification = z.infer<typeof NotificationSchema>;
|
||||||
51
client/src/lib/useTheme.ts
Normal file
51
client/src/lib/useTheme.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type Theme = "light" | "dark" | "system";
|
||||||
|
|
||||||
|
const useTheme = (): [Theme, (mode: Theme) => void] => {
|
||||||
|
const [theme, setTheme] = useState<Theme>(() => {
|
||||||
|
const savedTheme = localStorage.getItem("theme") as Theme;
|
||||||
|
return savedTheme || "system";
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const darkModeMediaQuery = window.matchMedia(
|
||||||
|
"(prefers-color-scheme: dark)",
|
||||||
|
);
|
||||||
|
const handleDarkModeChange = (e: MediaQueryListEvent) => {
|
||||||
|
if (theme === "system") {
|
||||||
|
updateDocumentTheme(e.matches ? "dark" : "light");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDocumentTheme = (newTheme: "light" | "dark") => {
|
||||||
|
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set initial theme based on current mode
|
||||||
|
if (theme === "system") {
|
||||||
|
updateDocumentTheme(darkModeMediaQuery.matches ? "dark" : "light");
|
||||||
|
} else {
|
||||||
|
updateDocumentTheme(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
darkModeMediaQuery.addEventListener("change", handleDarkModeChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
darkModeMediaQuery.removeEventListener("change", handleDarkModeChange);
|
||||||
|
};
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
theme,
|
||||||
|
useCallback((newTheme: Theme) => {
|
||||||
|
setTheme(newTheme);
|
||||||
|
localStorage.setItem("theme", newTheme);
|
||||||
|
if (newTheme !== "system") {
|
||||||
|
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
||||||
|
}
|
||||||
|
}, []),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useTheme;
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { ToastContainer } from 'react-toastify';
|
||||||
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
import App from "./App.tsx";
|
import App from "./App.tsx";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<App />
|
||||||
|
<ToastContainer />
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
import animate from "tailwindcss-animate";
|
||||||
export default {
|
export default {
|
||||||
darkMode: ["class"],
|
darkMode: ["class"],
|
||||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||||
@@ -53,5 +54,5 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [animate],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,12 @@ export default defineConfig({
|
|||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
minify: false,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
mcp-inspector.png
Normal file
BIN
mcp-inspector.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 294 KiB |
2436
package-lock.json
generated
2436
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -1,11 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector",
|
"name": "@modelcontextprotocol/inspector",
|
||||||
"version": "0.1.0",
|
"version": "0.2.4",
|
||||||
"private": true,
|
|
||||||
"description": "Model Context Protocol inspector",
|
"description": "Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
"homepage": "https://modelcontextprotocol.github.io",
|
"homepage": "https://modelcontextprotocol.io",
|
||||||
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -28,15 +27,20 @@
|
|||||||
"build": "npm run build-server && npm run build-client",
|
"build": "npm run build-server && npm run build-client",
|
||||||
"start-server": "cd server && npm run start",
|
"start-server": "cd server && npm run start",
|
||||||
"start-client": "cd client && npm run preview",
|
"start-client": "cd client && npm run preview",
|
||||||
"start": "./bin/cli.js",
|
"start": "node ./bin/cli.js",
|
||||||
"prepare": "npm run build",
|
"prepare": "npm run build",
|
||||||
"prettier-fix": "prettier --write ."
|
"prettier-fix": "prettier --write .",
|
||||||
|
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"concurrently": "^9.0.1"
|
"@modelcontextprotocol/inspector-client": "0.2.4",
|
||||||
|
"@modelcontextprotocol/inspector-server": "0.2.4",
|
||||||
|
"concurrently": "^9.0.1",
|
||||||
|
"spawn-rx": "^5.1.0",
|
||||||
|
"ts-node": "^10.9.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "3.3.3",
|
"@types/node": "^22.7.5",
|
||||||
"@types/node": "^22.7.5"
|
"prettier": "3.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-server",
|
"name": "@modelcontextprotocol/inspector-server",
|
||||||
"version": "0.1.0",
|
"version": "0.2.4",
|
||||||
"private": true,
|
|
||||||
"description": "Server-side application for the Model Context Protocol inspector",
|
"description": "Server-side application for the Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
"homepage": "https://modelcontextprotocol.github.io",
|
"homepage": "https://modelcontextprotocol.io",
|
||||||
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -28,7 +27,7 @@
|
|||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "*",
|
"@modelcontextprotocol/sdk": "^1.0.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^2.0.2",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import EventSource from "eventsource";
|
import EventSource from "eventsource";
|
||||||
|
import { parseArgs } from "node:util";
|
||||||
|
|
||||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||||
import {
|
import {
|
||||||
@@ -11,11 +12,20 @@ import {
|
|||||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import mcpProxy from "./mcpProxy.js";
|
import mcpProxy from "./mcpProxy.js";
|
||||||
|
import { findActualExecutable } from "spawn-rx";
|
||||||
|
|
||||||
// Polyfill EventSource for an SSE client in Node.js
|
// Polyfill EventSource for an SSE client in Node.js
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(global as any).EventSource = EventSource;
|
(global as any).EventSource = EventSource;
|
||||||
|
|
||||||
|
const { values } = parseArgs({
|
||||||
|
args: process.argv.slice(2),
|
||||||
|
options: {
|
||||||
|
env: { type: "string", default: "" },
|
||||||
|
args: { type: "string", default: "" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
@@ -28,20 +38,33 @@ const createTransport = async (query: express.Request["query"]) => {
|
|||||||
|
|
||||||
if (transportType === "stdio") {
|
if (transportType === "stdio") {
|
||||||
const command = query.command as string;
|
const command = query.command as string;
|
||||||
const args = (query.args as string).split(/\s+/);
|
const origArgs = (query.args as string).split(/\s+/);
|
||||||
const env = query.env ? JSON.parse(query.env as string) : undefined;
|
const env = query.env ? JSON.parse(query.env as string) : undefined;
|
||||||
|
|
||||||
|
const { cmd, args } = findActualExecutable(command, origArgs);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Stdio transport: command=${command}, args=${args}, env=${JSON.stringify(env)}`,
|
`Stdio transport: command=${cmd}, args=${args}, env=${JSON.stringify(env)}`,
|
||||||
);
|
);
|
||||||
const transport = new StdioClientTransport({ command, args, env });
|
|
||||||
|
const transport = new StdioClientTransport({
|
||||||
|
command: cmd,
|
||||||
|
args,
|
||||||
|
env,
|
||||||
|
stderr: "pipe",
|
||||||
|
});
|
||||||
|
|
||||||
await transport.start();
|
await transport.start();
|
||||||
|
|
||||||
console.log("Spawned stdio transport");
|
console.log("Spawned stdio transport");
|
||||||
return transport;
|
return transport;
|
||||||
} else if (transportType === "sse") {
|
} else if (transportType === "sse") {
|
||||||
const url = query.url as string;
|
const url = query.url as string;
|
||||||
console.log(`SSE transport: url=${url}`);
|
console.log(`SSE transport: url=${url}`);
|
||||||
|
|
||||||
const transport = new SSEClientTransport(new URL(url));
|
const transport = new SSEClientTransport(new URL(url));
|
||||||
await transport.start();
|
await transport.start();
|
||||||
|
|
||||||
console.log("Connected to SSE transport");
|
console.log("Connected to SSE transport");
|
||||||
return transport;
|
return transport;
|
||||||
} else {
|
} else {
|
||||||
@@ -51,47 +74,79 @@ const createTransport = async (query: express.Request["query"]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
app.get("/sse", async (req, res) => {
|
app.get("/sse", async (req, res) => {
|
||||||
console.log("New SSE connection");
|
try {
|
||||||
|
console.log("New SSE connection");
|
||||||
|
|
||||||
const backingServerTransport = await createTransport(req.query);
|
const backingServerTransport = await createTransport(req.query);
|
||||||
|
|
||||||
console.log("Connected MCP client to backing server transport");
|
console.log("Connected MCP client to backing server transport");
|
||||||
|
|
||||||
const webAppTransport = new SSEServerTransport("/message", res);
|
const webAppTransport = new SSEServerTransport("/message", res);
|
||||||
console.log("Created web app transport");
|
console.log("Created web app transport");
|
||||||
|
|
||||||
webAppTransports.push(webAppTransport);
|
webAppTransports.push(webAppTransport);
|
||||||
console.log("Created web app transport");
|
console.log("Created web app transport");
|
||||||
|
|
||||||
await webAppTransport.start();
|
await webAppTransport.start();
|
||||||
|
|
||||||
mcpProxy({
|
if (backingServerTransport instanceof StdioClientTransport) {
|
||||||
transportToClient: webAppTransport,
|
backingServerTransport.stderr!.on("data", (chunk) => {
|
||||||
transportToServer: backingServerTransport,
|
webAppTransport.send({
|
||||||
onerror: (error) => {
|
jsonrpc: "2.0",
|
||||||
console.error(error);
|
method: "notifications/stderr",
|
||||||
},
|
params: {
|
||||||
});
|
content: chunk.toString(),
|
||||||
console.log("Set up MCP proxy");
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpProxy({
|
||||||
|
transportToClient: webAppTransport,
|
||||||
|
transportToServer: backingServerTransport,
|
||||||
|
onerror: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Set up MCP proxy");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in /sse route:", error);
|
||||||
|
res.status(500).json(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/message", async (req, res) => {
|
app.post("/message", async (req, res) => {
|
||||||
const sessionId = req.query.sessionId;
|
try {
|
||||||
console.log(`Received message for sessionId ${sessionId}`);
|
const sessionId = req.query.sessionId;
|
||||||
|
console.log(`Received message for sessionId ${sessionId}`);
|
||||||
|
|
||||||
const transport = webAppTransports.find((t) => t.sessionId === sessionId);
|
const transport = webAppTransports.find((t) => t.sessionId === sessionId);
|
||||||
if (!transport) {
|
if (!transport) {
|
||||||
res.status(404).send("Session not found");
|
res.status(404).end("Session not found");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
await transport.handlePostMessage(req, res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in /message route:", error);
|
||||||
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
await transport.handlePostMessage(req, res);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/default-environment", (req, res) => {
|
app.get("/config", (req, res) => {
|
||||||
res.json(getDefaultEnvironment());
|
try {
|
||||||
|
const defaultEnvironment = getDefaultEnvironment();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
defaultEnvironment,
|
||||||
|
defaultCommand: values.env,
|
||||||
|
defaultArgs: values.args,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in /config route:", error);
|
||||||
|
res.status(500).json(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {});
|
||||||
console.log(`Server is running on port ${PORT}`);
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user