Compare commits
105 Commits
davidsp/em
...
0.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
6fdb2903a4 | ||
|
|
b9d6695fbf | ||
|
|
3789ef984a | ||
|
|
e1aa419332 | ||
|
|
94e4e92618 | ||
|
|
148e84388a | ||
|
|
6147640656 | ||
|
|
1ddd2d7aaa | ||
|
|
94ddfed1ac | ||
|
|
448910d986 | ||
|
|
3a9b08bd37 | ||
|
|
bce3a7b8d6 | ||
|
|
196f2f801d | ||
|
|
a0d8ec1e7e | ||
|
|
6759c461f9 | ||
|
|
ea4484cc04 | ||
|
|
171b59bec6 | ||
|
|
2867173e7b | ||
|
|
57f0c49154 | ||
|
|
5337baa116 | ||
|
|
afefcb3fa5 | ||
|
|
76e2cf6fdc | ||
|
|
193032533b | ||
|
|
f3406ca43d | ||
|
|
645f2e942e | ||
|
|
d80214d0a2 | ||
|
|
97b67ca1ae | ||
|
|
216c8a15d5 |
43
.github/workflows/main.yml
vendored
43
.github/workflows/main.yml
vendored
@@ -4,6 +4,8 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -17,21 +19,34 @@ jobs:
|
|||||||
node-version: 18
|
node-version: 18
|
||||||
cache: npm
|
cache: npm
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: modelcontextprotocol/typescript-sdk
|
|
||||||
ssh-key: ${{ secrets.TYPESCRIPT_SDK_KEY }}
|
|
||||||
path: packages/@modelcontextprotocol/sdk
|
|
||||||
|
|
||||||
- run: npm ci
|
|
||||||
working-directory: packages/@modelcontextprotocol/sdk
|
|
||||||
|
|
||||||
- run: npm pack
|
|
||||||
working-directory: packages/@modelcontextprotocol/sdk
|
|
||||||
|
|
||||||
- run: npm install --save packages/@modelcontextprotocol/sdk/modelcontextprotocol-sdk-*.tgz
|
|
||||||
|
|
||||||
# Working around https://github.com/npm/cli/issues/4828
|
# Working around https://github.com/npm/cli/issues/4828
|
||||||
# - 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 }}
|
||||||
|
|||||||
3
.npmrc
3
.npmrc
@@ -1 +1,2 @@
|
|||||||
registry = "https://registry.npmjs.org/"
|
registry="https://registry.npmjs.org/"
|
||||||
|
@modelcontextprotocol:registry="https://registry.npmjs.org/"
|
||||||
|
|||||||
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.
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Anthropic, PBC
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
28
README.md
28
README.md
@@ -2,30 +2,18 @@
|
|||||||
|
|
||||||
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
|
It can be run easily from `npx`. For example, in a folder where there's a built JavaScript server at `build/index.js`:
|
||||||
|
|
||||||
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:
|
```
|
||||||
|
npx @modelcontextprotocol/inspector build/index.js
|
||||||
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.
|
|
||||||
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:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can run it in dev mode via:
|
You can also pass arguments along to the server:
|
||||||
|
|
||||||
```bash
|
```
|
||||||
npm run dev
|
npx @modelcontextprotocol/inspector build/index.js arg1 arg2 ...
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start both the client and server.
|
## License
|
||||||
|
|
||||||
To run in production mode:
|
This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|||||||
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).
|
||||||
65
bin/cli.js
Executable file
65
bin/cli.js
Executable file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { join, dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import concurrently from "concurrently";
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
// Get command line arguments
|
||||||
|
const [, , command, ...mcpServerArgs] = process.argv;
|
||||||
|
|
||||||
|
const inspectorServerPath = join(__dirname, "../server/build/index.js");
|
||||||
|
|
||||||
|
// Path to the client entry point
|
||||||
|
const inspectorClientPath = join(__dirname, "../client/bin/cli.js");
|
||||||
|
|
||||||
|
console.log("Starting MCP inspector...");
|
||||||
|
|
||||||
|
function escapeArg(arg) {
|
||||||
|
if (arg.includes(" ") || arg.includes("'") || arg.includes('"')) {
|
||||||
|
return `\\"${arg.replace(/"/g, '\\\\\\"')}\\"`;
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverCommand = [
|
||||||
|
`node`,
|
||||||
|
inspectorServerPath,
|
||||||
|
command ? `--env ${escapeArg(command)}` : "",
|
||||||
|
mcpServerArgs.length
|
||||||
|
? `--args="${mcpServerArgs.map(escapeArg).join(" ")}"`
|
||||||
|
: "",
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
const CLIENT_PORT = process.env.CLIENT_PORT ?? "";
|
||||||
|
const SERVER_PORT = process.env.SERVER_PORT ?? "";
|
||||||
|
|
||||||
|
const { result } = concurrently(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
command: `PORT=${SERVER_PORT} ${serverCommand}`,
|
||||||
|
name: "server",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: `PORT=${CLIENT_PORT} node ${inspectorClientPath}`,
|
||||||
|
name: "client",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
prefix: "name",
|
||||||
|
killOthers: ["failure", "success"],
|
||||||
|
restartTries: 3,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT || 5173} 🚀`,
|
||||||
|
);
|
||||||
|
|
||||||
|
result.catch((err) => {
|
||||||
|
console.error("An error occurred:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
16
client/bin/cli.js
Executable file
16
client/bin/cli.js
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { join, dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import handler from "serve-handler";
|
||||||
|
import http from "http";
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const distPath = join(__dirname, "../dist");
|
||||||
|
|
||||||
|
const server = http.createServer((request, response) => {
|
||||||
|
return handler(request, response, { public: distPath });
|
||||||
|
});
|
||||||
|
|
||||||
|
const port = process.env.PORT || 5173;
|
||||||
|
server.listen(port, () => {});
|
||||||
@@ -17,4 +17,4 @@
|
|||||||
"lib": "@/lib",
|
"lib": "@/lib",
|
||||||
"hooks": "@/hooks"
|
"hooks": "@/hooks"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,8 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "@modelcontextprotocol/inspector-client",
|
||||||
"private": true,
|
"version": "0.2.1",
|
||||||
"version": "0.0.0",
|
"description": "Client-side application for the Model Context Protocol inspector",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
"homepage": "https://modelcontextprotocol.io",
|
||||||
|
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"mcp-inspector-client": "./bin/cli.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
@@ -10,7 +21,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "*",
|
"@modelcontextprotocol/sdk": "0.7.0",
|
||||||
"@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",
|
||||||
@@ -21,6 +32,8 @@
|
|||||||
"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",
|
||||||
"tailwind-merge": "^2.5.3",
|
"tailwind-merge": "^2.5.3",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
@@ -30,6 +43,7 @@
|
|||||||
"@types/node": "^22.7.5",
|
"@types/node": "^22.7.5",
|
||||||
"@types/react": "^18.3.10",
|
"@types/react": "^18.3.10",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@types/serve-handler": "^6.1.4",
|
||||||
"@vitejs/plugin-react": "^4.3.2",
|
"@vitejs/plugin-react": "^4.3.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.11.1",
|
"eslint": "^9.11.1",
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ export default {
|
|||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
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,86 +1,100 @@
|
|||||||
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 {
|
||||||
CallToolResultSchema,
|
ClientNotification,
|
||||||
ClientRequest,
|
ClientRequest,
|
||||||
|
CompatibilityCallToolResult,
|
||||||
|
CompatibilityCallToolResultSchema,
|
||||||
CreateMessageRequestSchema,
|
CreateMessageRequestSchema,
|
||||||
CreateMessageResult,
|
CreateMessageResult,
|
||||||
EmptyResultSchema,
|
EmptyResultSchema,
|
||||||
GetPromptResultSchema,
|
GetPromptResultSchema,
|
||||||
ListPromptsResultSchema,
|
ListPromptsResultSchema,
|
||||||
ListResourcesResultSchema,
|
ListResourcesResultSchema,
|
||||||
|
ListResourceTemplatesResultSchema,
|
||||||
|
ListRootsRequestSchema,
|
||||||
ListToolsResultSchema,
|
ListToolsResultSchema,
|
||||||
ProgressNotificationSchema,
|
ProgressNotificationSchema,
|
||||||
ReadResourceResultSchema,
|
ReadResourceResultSchema,
|
||||||
|
Request,
|
||||||
Resource,
|
Resource,
|
||||||
|
ResourceTemplate,
|
||||||
|
Result,
|
||||||
|
Root,
|
||||||
ServerNotification,
|
ServerNotification,
|
||||||
Tool,
|
Tool,
|
||||||
} 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,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { AnyZodObject } from "zod";
|
import { toast } from "react-toastify";
|
||||||
|
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 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";
|
||||||
|
import useDarkModeSync from "./lib/useDarkModeSync";
|
||||||
|
|
||||||
|
const DEFAULT_REQUEST_TIMEOUT_MSEC = 10000;
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [connectionStatus, setConnectionStatus] = useState<
|
const [connectionStatus, setConnectionStatus] = useState<
|
||||||
"disconnected" | "connected" | "error"
|
"disconnected" | "connected" | "error"
|
||||||
>("disconnected");
|
>("disconnected");
|
||||||
const [resources, setResources] = useState<Resource[]>([]);
|
const [resources, setResources] = useState<Resource[]>([]);
|
||||||
|
const [resourceTemplates, setResourceTemplates] = useState<
|
||||||
|
ResourceTemplate[]
|
||||||
|
>([]);
|
||||||
const [resourceContent, setResourceContent] = useState<string>("");
|
const [resourceContent, setResourceContent] = useState<string>("");
|
||||||
const [prompts, setPrompts] = useState<Prompt[]>([]);
|
const [prompts, setPrompts] = useState<Prompt[]>([]);
|
||||||
const [promptContent, setPromptContent] = useState<string>("");
|
const [promptContent, setPromptContent] = useState<string>("");
|
||||||
const [tools, setTools] = useState<Tool[]>([]);
|
const [tools, setTools] = useState<Tool[]>([]);
|
||||||
const [toolResult, setToolResult] = useState<string>("");
|
const [toolResult, setToolResult] =
|
||||||
const [error, setError] = useState<string | null>(null);
|
useState<CompatibilityCallToolResult | 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 [url, setUrl] = 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 [env, setEnv] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
const [pendingSampleRequests, setPendingSampleRequests] = useState<
|
const [pendingSampleRequests, setPendingSampleRequests] = useState<
|
||||||
Array<
|
Array<
|
||||||
@@ -91,6 +105,7 @@ const App = () => {
|
|||||||
>
|
>
|
||||||
>([]);
|
>([]);
|
||||||
const nextRequestId = useRef(0);
|
const nextRequestId = useRef(0);
|
||||||
|
const rootsRef = useRef<Root[]>([]);
|
||||||
|
|
||||||
const handleApproveSampling = (id: number, result: CreateMessageResult) => {
|
const handleApproveSampling = (id: number, result: CreateMessageResult) => {
|
||||||
setPendingSampleRequests((prev) => {
|
setPendingSampleRequests((prev) => {
|
||||||
@@ -116,11 +131,59 @@ const App = () => {
|
|||||||
const [nextResourceCursor, setNextResourceCursor] = useState<
|
const [nextResourceCursor, setNextResourceCursor] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>();
|
>();
|
||||||
|
const [nextResourceTemplateCursor, setNextResourceTemplateCursor] = useState<
|
||||||
|
string | undefined
|
||||||
|
>();
|
||||||
const [nextPromptCursor, setNextPromptCursor] = useState<
|
const [nextPromptCursor, setNextPromptCursor] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>();
|
>();
|
||||||
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);
|
||||||
|
|
||||||
|
useDarkModeSync();
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -130,27 +193,96 @@ const App = () => {
|
|||||||
localStorage.setItem("lastArgs", args);
|
localStorage.setItem("lastArgs", args);
|
||||||
}, [args]);
|
}, [args]);
|
||||||
|
|
||||||
const pushHistory = (request: object, response: object) => {
|
useEffect(() => {
|
||||||
|
fetch("http://localhost:3000/config")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
setEnv(data.defaultEnvironment);
|
||||||
|
if (data.defaultCommand) {
|
||||||
|
setCommand(data.defaultCommand);
|
||||||
|
}
|
||||||
|
if (data.defaultArgs) {
|
||||||
|
setArgs(data.defaultArgs);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
console.error("Error fetching default environment:", error),
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
rootsRef.current = roots;
|
||||||
|
}, [roots]);
|
||||||
|
|
||||||
|
const pushHistory = (request: object, response?: object) => {
|
||||||
setRequestHistory((prev) => [
|
setRequestHistory((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{ request: JSON.stringify(request), response: JSON.stringify(response) },
|
{
|
||||||
|
request: JSON.stringify(request),
|
||||||
|
response: response !== undefined ? JSON.stringify(response) : undefined,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeRequest = async <T extends AnyZodObject>(
|
const clearError = (tabKey: keyof typeof errors) => {
|
||||||
|
setErrors((prev) => ({ ...prev, [tabKey]: null }));
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendNotification = async (notification: ClientNotification) => {
|
||||||
|
if (!mcpClient) {
|
||||||
|
throw new Error("MCP client not connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mcpClient.notification(notification);
|
||||||
|
pushHistory(notification);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
toast.error((e as Error).message ?? String(e));
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -162,11 +294,29 @@ 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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listResourceTemplates = async () => {
|
||||||
|
const response = await makeRequest(
|
||||||
|
{
|
||||||
|
method: "resources/templates/list" as const,
|
||||||
|
params: nextResourceTemplateCursor
|
||||||
|
? { cursor: nextResourceTemplateCursor }
|
||||||
|
: {},
|
||||||
|
},
|
||||||
|
ListResourceTemplatesResultSchema,
|
||||||
|
"resources",
|
||||||
|
);
|
||||||
|
setResourceTemplates(
|
||||||
|
resourceTemplates.concat(response.resourceTemplates ?? []),
|
||||||
|
);
|
||||||
|
setNextResourceTemplateCursor(response.nextCursor);
|
||||||
|
};
|
||||||
|
|
||||||
const readResource = async (uri: string) => {
|
const readResource = async (uri: string) => {
|
||||||
const response = await makeRequest(
|
const response = await makeRequest(
|
||||||
{
|
{
|
||||||
@@ -174,6 +324,7 @@ const App = () => {
|
|||||||
params: { uri },
|
params: { uri },
|
||||||
},
|
},
|
||||||
ReadResourceResultSchema,
|
ReadResourceResultSchema,
|
||||||
|
"resources",
|
||||||
);
|
);
|
||||||
setResourceContent(JSON.stringify(response, null, 2));
|
setResourceContent(JSON.stringify(response, null, 2));
|
||||||
};
|
};
|
||||||
@@ -185,6 +336,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);
|
||||||
@@ -197,6 +349,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));
|
||||||
};
|
};
|
||||||
@@ -208,6 +361,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);
|
||||||
@@ -225,17 +379,33 @@ const App = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CallToolResultSchema,
|
CompatibilityCallToolResultSchema,
|
||||||
|
"tools",
|
||||||
);
|
);
|
||||||
setToolResult(JSON.stringify(response.toolResult, null, 2));
|
setToolResult(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRootsChange = async () => {
|
||||||
|
await sendNotification({ method: "notifications/roots/list_changed" });
|
||||||
};
|
};
|
||||||
|
|
||||||
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("http://localhost:3000/sse");
|
||||||
|
|
||||||
@@ -243,13 +413,12 @@ const App = () => {
|
|||||||
if (transportType === "stdio") {
|
if (transportType === "stdio") {
|
||||||
backendUrl.searchParams.append("command", command);
|
backendUrl.searchParams.append("command", command);
|
||||||
backendUrl.searchParams.append("args", args);
|
backendUrl.searchParams.append("args", args);
|
||||||
|
backendUrl.searchParams.append("env", JSON.stringify(env));
|
||||||
} else {
|
} else {
|
||||||
backendUrl.searchParams.append("url", url);
|
backendUrl.searchParams.append("url", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientTransport = new SSEClientTransport(backendUrl);
|
const clientTransport = new SSEClientTransport(backendUrl);
|
||||||
await client.connect(clientTransport);
|
|
||||||
|
|
||||||
client.setNotificationHandler(
|
client.setNotificationHandler(
|
||||||
ProgressNotificationSchema,
|
ProgressNotificationSchema,
|
||||||
(notification) => {
|
(notification) => {
|
||||||
@@ -260,6 +429,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) => [
|
||||||
@@ -269,6 +450,10 @@ const App = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
client.setRequestHandler(ListRootsRequestSchema, async () => {
|
||||||
|
return { roots: rootsRef.current };
|
||||||
|
});
|
||||||
|
|
||||||
setMcpClient(client);
|
setMcpClient(client);
|
||||||
setConnectionStatus("connected");
|
setConnectionStatus("connected");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -278,160 +463,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}
|
||||||
|
url={url}
|
||||||
|
setUrl={setUrl}
|
||||||
|
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)"
|
</TabsTrigger>
|
||||||
value={args}
|
<TabsTrigger value="roots">
|
||||||
onChange={(e) => setArgs(e.target.value)}
|
<FolderTree className="w-4 h-4 mr-2" />
|
||||||
/>
|
Roots
|
||||||
</>
|
</TabsTrigger>
|
||||||
) : (
|
</TabsList>
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
{mcpClient ? (
|
|
||||||
<Tabs defaultValue="resources" className="w-full p-4">
|
|
||||||
<TabsList className="mb-4 p-0">
|
|
||||||
<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>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<ResourcesTab
|
<ResourcesTab
|
||||||
resources={resources}
|
resources={resources}
|
||||||
listResources={listResources}
|
resourceTemplates={resourceTemplates}
|
||||||
readResource={readResource}
|
listResources={() => {
|
||||||
selectedResource={selectedResource}
|
clearError("resources");
|
||||||
setSelectedResource={setSelectedResource}
|
listResources();
|
||||||
resourceContent={resourceContent}
|
}}
|
||||||
nextCursor={nextResourceCursor}
|
listResourceTemplates={() => {
|
||||||
error={error}
|
clearError("resources");
|
||||||
/>
|
listResourceTemplates();
|
||||||
<PromptsTab
|
}}
|
||||||
prompts={prompts}
|
readResource={(uri) => {
|
||||||
listPrompts={listPrompts}
|
clearError("resources");
|
||||||
getPrompt={getPrompt}
|
readResource(uri);
|
||||||
selectedPrompt={selectedPrompt}
|
}}
|
||||||
setSelectedPrompt={setSelectedPrompt}
|
selectedResource={selectedResource}
|
||||||
promptContent={promptContent}
|
setSelectedResource={(resource) => {
|
||||||
nextCursor={nextPromptCursor}
|
clearError("resources");
|
||||||
error={error}
|
setSelectedResource(resource);
|
||||||
/>
|
}}
|
||||||
<RequestsTab />
|
resourceContent={resourceContent}
|
||||||
<ToolsTab
|
nextCursor={nextResourceCursor}
|
||||||
tools={tools}
|
nextTemplateCursor={nextResourceTemplateCursor}
|
||||||
listTools={listTools}
|
error={errors.resources}
|
||||||
callTool={callTool}
|
/>
|
||||||
selectedTool={selectedTool}
|
<PromptsTab
|
||||||
setSelectedTool={(tool) => {
|
prompts={prompts}
|
||||||
setSelectedTool(tool);
|
listPrompts={() => {
|
||||||
setToolResult("");
|
clearError("prompts");
|
||||||
}}
|
listPrompts();
|
||||||
toolResult={toolResult}
|
}}
|
||||||
nextCursor={nextToolCursor}
|
getPrompt={(name, args) => {
|
||||||
error={error}
|
clearError("prompts");
|
||||||
/>
|
getPrompt(name, args);
|
||||||
<ConsoleTab />
|
}}
|
||||||
<PingTab
|
selectedPrompt={selectedPrompt}
|
||||||
onPingClick={() => {
|
setSelectedPrompt={(prompt) => {
|
||||||
void makeRequest(
|
clearError("prompts");
|
||||||
{
|
setSelectedPrompt(prompt);
|
||||||
method: "ping" as const,
|
}}
|
||||||
},
|
promptContent={promptContent}
|
||||||
EmptyResultSchema,
|
nextCursor={nextPromptCursor}
|
||||||
);
|
error={errors.prompts}
|
||||||
}}
|
/>
|
||||||
/>
|
<ToolsTab
|
||||||
<SamplingTab
|
tools={tools}
|
||||||
pendingRequests={pendingSampleRequests}
|
listTools={() => {
|
||||||
onApprove={handleApproveSampling}
|
clearError("tools");
|
||||||
onReject={handleRejectSampling}
|
listTools();
|
||||||
/>
|
}}
|
||||||
</div>
|
callTool={(name, params) => {
|
||||||
</Tabs>
|
clearError("tools");
|
||||||
) : (
|
callTool(name, params);
|
||||||
<div className="flex items-center justify-center h-full">
|
}}
|
||||||
<p className="text-lg text-gray-500">
|
selectedTool={selectedTool}
|
||||||
Connect to an MCP server to start inspecting
|
setSelectedTool={(tool) => {
|
||||||
</p>
|
clearError("tools");
|
||||||
|
setSelectedTool(tool);
|
||||||
|
setToolResult(null);
|
||||||
|
}}
|
||||||
|
toolResult={toolResult}
|
||||||
|
nextCursor={nextToolCursor}
|
||||||
|
error={errors.tools}
|
||||||
|
/>
|
||||||
|
<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 |
@@ -6,7 +6,7 @@ const HistoryAndNotifications = ({
|
|||||||
requestHistory,
|
requestHistory,
|
||||||
serverNotifications,
|
serverNotifications,
|
||||||
}: {
|
}: {
|
||||||
requestHistory: Array<{ request: string; response: string | null }>;
|
requestHistory: Array<{ request: string; response?: string }>;
|
||||||
serverNotifications: ServerNotification[];
|
serverNotifications: ServerNotification[];
|
||||||
}) => {
|
}) => {
|
||||||
const [expandedRequests, setExpandedRequests] = useState<{
|
const [expandedRequests, setExpandedRequests] = useState<{
|
||||||
@@ -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;
|
|
||||||
@@ -1,88 +1,199 @@
|
|||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import { TabsContent } from "@/components/ui/tabs";
|
import { TabsContent } from "@/components/ui/tabs";
|
||||||
import { ListResourcesResult, Resource } from "@modelcontextprotocol/sdk/types.js";
|
import {
|
||||||
|
ListResourcesResult,
|
||||||
|
Resource,
|
||||||
|
ResourceTemplate,
|
||||||
|
ListResourceTemplatesResult,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { AlertCircle, ChevronRight, FileText, RefreshCw } from "lucide-react";
|
import { AlertCircle, ChevronRight, FileText, RefreshCw } from "lucide-react";
|
||||||
import ListPane from "./ListPane";
|
import ListPane from "./ListPane";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const ResourcesTab = ({
|
const ResourcesTab = ({
|
||||||
resources,
|
resources,
|
||||||
|
resourceTemplates,
|
||||||
listResources,
|
listResources,
|
||||||
|
listResourceTemplates,
|
||||||
readResource,
|
readResource,
|
||||||
selectedResource,
|
selectedResource,
|
||||||
setSelectedResource,
|
setSelectedResource,
|
||||||
resourceContent,
|
resourceContent,
|
||||||
nextCursor,
|
nextCursor,
|
||||||
|
nextTemplateCursor,
|
||||||
error,
|
error,
|
||||||
}: {
|
}: {
|
||||||
resources: Resource[];
|
resources: Resource[];
|
||||||
|
resourceTemplates: ResourceTemplate[];
|
||||||
listResources: () => void;
|
listResources: () => void;
|
||||||
|
listResourceTemplates: () => void;
|
||||||
readResource: (uri: string) => void;
|
readResource: (uri: string) => void;
|
||||||
selectedResource: Resource | null;
|
selectedResource: Resource | null;
|
||||||
setSelectedResource: (resource: Resource) => void;
|
setSelectedResource: (resource: Resource | null) => void;
|
||||||
resourceContent: string;
|
resourceContent: string;
|
||||||
nextCursor: ListResourcesResult["nextCursor"];
|
nextCursor: ListResourcesResult["nextCursor"];
|
||||||
|
nextTemplateCursor: ListResourceTemplatesResult["nextCursor"];
|
||||||
error: string | null;
|
error: string | null;
|
||||||
}) => (
|
}) => {
|
||||||
<TabsContent value="resources" className="grid grid-cols-2 gap-4">
|
const [selectedTemplate, setSelectedTemplate] =
|
||||||
<ListPane
|
useState<ResourceTemplate | null>(null);
|
||||||
items={resources}
|
const [templateValues, setTemplateValues] = useState<Record<string, string>>(
|
||||||
listItems={listResources}
|
{},
|
||||||
setSelectedItem={(resource) => {
|
);
|
||||||
setSelectedResource(resource);
|
|
||||||
readResource(resource.uri);
|
|
||||||
}}
|
|
||||||
renderItem={(resource) => (
|
|
||||||
<div className="flex items-center w-full">
|
|
||||||
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
|
|
||||||
<span className="flex-1 truncate" title={resource.uri.toString()}>
|
|
||||||
{resource.name}
|
|
||||||
</span>
|
|
||||||
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
title="Resources"
|
|
||||||
buttonText={nextCursor ? "List More Resources" : "List Resources"}
|
|
||||||
isButtonDisabled={!nextCursor && resources.length > 0}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow">
|
const fillTemplate = (
|
||||||
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
|
template: string,
|
||||||
<h3 className="font-semibold truncate" title={selectedResource?.name}>
|
values: Record<string, string>,
|
||||||
{selectedResource ? selectedResource.name : "Select a resource"}
|
): string => {
|
||||||
</h3>
|
return template.replace(
|
||||||
{selectedResource && (
|
/{([^}]+)}/g,
|
||||||
<Button
|
(_, key) => values[key] || `{${key}}`,
|
||||||
variant="outline"
|
);
|
||||||
size="sm"
|
};
|
||||||
onClick={() => readResource(selectedResource.uri)}
|
|
||||||
|
const handleReadTemplateResource = () => {
|
||||||
|
if (selectedTemplate) {
|
||||||
|
const uri = fillTemplate(selectedTemplate.uriTemplate, templateValues);
|
||||||
|
readResource(uri);
|
||||||
|
setSelectedTemplate(null);
|
||||||
|
// We don't have the full Resource object here, so we create a partial one
|
||||||
|
setSelectedResource({ uri, name: uri } as Resource);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabsContent value="resources" className="grid grid-cols-3 gap-4">
|
||||||
|
<ListPane
|
||||||
|
items={resources}
|
||||||
|
listItems={listResources}
|
||||||
|
setSelectedItem={(resource) => {
|
||||||
|
setSelectedResource(resource);
|
||||||
|
readResource(resource.uri);
|
||||||
|
setSelectedTemplate(null);
|
||||||
|
}}
|
||||||
|
renderItem={(resource) => (
|
||||||
|
<div className="flex items-center w-full">
|
||||||
|
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
|
||||||
|
<span className="flex-1 truncate" title={resource.uri.toString()}>
|
||||||
|
{resource.name}
|
||||||
|
</span>
|
||||||
|
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
title="Resources"
|
||||||
|
buttonText={nextCursor ? "List More Resources" : "List Resources"}
|
||||||
|
isButtonDisabled={!nextCursor && resources.length > 0}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ListPane
|
||||||
|
items={resourceTemplates}
|
||||||
|
listItems={listResourceTemplates}
|
||||||
|
setSelectedItem={(template) => {
|
||||||
|
setSelectedTemplate(template);
|
||||||
|
setSelectedResource(null);
|
||||||
|
setTemplateValues({});
|
||||||
|
}}
|
||||||
|
renderItem={(template) => (
|
||||||
|
<div className="flex items-center w-full">
|
||||||
|
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
|
||||||
|
<span className="flex-1 truncate" title={template.uriTemplate}>
|
||||||
|
{template.name}
|
||||||
|
</span>
|
||||||
|
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
title="Resource Templates"
|
||||||
|
buttonText={
|
||||||
|
nextTemplateCursor ? "List More Templates" : "List Templates"
|
||||||
|
}
|
||||||
|
isButtonDisabled={!nextTemplateCursor && resourceTemplates.length > 0}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="bg-card rounded-lg shadow">
|
||||||
|
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
|
||||||
|
<h3
|
||||||
|
className="font-semibold truncate"
|
||||||
|
title={selectedResource?.name || selectedTemplate?.name}
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-4 h-4 mr-2" />
|
{selectedResource
|
||||||
Refresh
|
? selectedResource.name
|
||||||
</Button>
|
: selectedTemplate
|
||||||
)}
|
? selectedTemplate.name
|
||||||
|
: "Select a resource or template"}
|
||||||
|
</h3>
|
||||||
|
{selectedResource && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => readResource(selectedResource.uri)}
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-4 h-4 mr-2" />
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="p-4">
|
||||||
|
{error ? (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertTitle>Error</AlertTitle>
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
) : selectedResource ? (
|
||||||
|
<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}
|
||||||
|
</pre>
|
||||||
|
) : selectedTemplate ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
{selectedTemplate.description}
|
||||||
|
</p>
|
||||||
|
{selectedTemplate.uriTemplate
|
||||||
|
.match(/{([^}]+)}/g)
|
||||||
|
?.map((param) => {
|
||||||
|
const key = param.slice(1, -1);
|
||||||
|
return (
|
||||||
|
<div key={key}>
|
||||||
|
<label
|
||||||
|
htmlFor={key}
|
||||||
|
className="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id={key}
|
||||||
|
value={templateValues[key] || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setTemplateValues({
|
||||||
|
...templateValues,
|
||||||
|
[key]: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<Button
|
||||||
|
onClick={handleReadTemplateResource}
|
||||||
|
disabled={Object.keys(templateValues).length === 0}
|
||||||
|
>
|
||||||
|
Read Resource
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Alert>
|
||||||
|
<AlertDescription>
|
||||||
|
Select a resource or template from the list to view its contents
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4">
|
</TabsContent>
|
||||||
{error ? (
|
);
|
||||||
<Alert variant="destructive">
|
};
|
||||||
<AlertCircle className="h-4 w-4" />
|
|
||||||
<AlertTitle>Error</AlertTitle>
|
|
||||||
<AlertDescription>{error}</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
) : selectedResource ? (
|
|
||||||
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-96 whitespace-pre-wrap break-words">
|
|
||||||
{resourceContent}
|
|
||||||
</pre>
|
|
||||||
) : (
|
|
||||||
<Alert>
|
|
||||||
<AlertDescription>
|
|
||||||
Select a resource from the list to view its contents
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ResourcesTab;
|
export default ResourcesTab;
|
||||||
|
|||||||
77
client/src/components/RootsTab.tsx
Normal file
77
client/src/components/RootsTab.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { TabsContent } from "@/components/ui/tabs";
|
||||||
|
import { Root } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import { Plus, Minus, Save } from "lucide-react";
|
||||||
|
|
||||||
|
const RootsTab = ({
|
||||||
|
roots,
|
||||||
|
setRoots,
|
||||||
|
onRootsChange,
|
||||||
|
}: {
|
||||||
|
roots: Root[];
|
||||||
|
setRoots: React.Dispatch<React.SetStateAction<Root[]>>;
|
||||||
|
onRootsChange: () => void;
|
||||||
|
}) => {
|
||||||
|
const addRoot = () => {
|
||||||
|
setRoots((currentRoots) => [...currentRoots, { uri: "file://", name: "" }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeRoot = (index: number) => {
|
||||||
|
setRoots((currentRoots) => currentRoots.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateRoot = (index: number, field: keyof Root, value: string) => {
|
||||||
|
setRoots((currentRoots) =>
|
||||||
|
currentRoots.map((root, i) =>
|
||||||
|
i === index ? { ...root, [field]: value } : root,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
onRootsChange();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabsContent value="roots" className="space-y-4">
|
||||||
|
<Alert>
|
||||||
|
<AlertDescription>
|
||||||
|
Configure the root directories that the server can access
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
{roots.map((root, index) => (
|
||||||
|
<div key={index} className="flex gap-2 items-center">
|
||||||
|
<Input
|
||||||
|
placeholder="file:// URI"
|
||||||
|
value={root.uri}
|
||||||
|
onChange={(e) => updateRoot(index, "uri", e.target.value)}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => removeRoot(index)}
|
||||||
|
>
|
||||||
|
<Minus className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="outline" onClick={addRoot}>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
Add Root
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSave}>
|
||||||
|
<Save className="h-4 w-4 mr-2" />
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RootsTab;
|
||||||
@@ -1,39 +1,219 @@
|
|||||||
import { Menu, Settings } from "lucide-react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import { Play, ChevronDown, ChevronRight, Settings } 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 }) => (
|
interface SidebarProps {
|
||||||
<div className="w-64 bg-white border-r border-gray-200">
|
connectionStatus: "disconnected" | "connected" | "error";
|
||||||
<div className="flex items-center p-4 border-b border-gray-200">
|
transportType: "stdio" | "sse";
|
||||||
<Menu className="w-6 h-6 text-gray-500" />
|
setTransportType: (type: "stdio" | "sse") => void;
|
||||||
<h1 className="ml-2 text-lg font-semibold">MCP Inspector</h1>
|
command: string;
|
||||||
</div>
|
setCommand: (command: string) => void;
|
||||||
|
args: string;
|
||||||
|
setArgs: (args: string) => void;
|
||||||
|
url: string;
|
||||||
|
setUrl: (url: string) => void;
|
||||||
|
env: Record<string, string>;
|
||||||
|
setEnv: (env: Record<string, string>) => void;
|
||||||
|
onConnect: () => void;
|
||||||
|
stdErrNotifications: StdErrNotification[];
|
||||||
|
}
|
||||||
|
|
||||||
<div className="p-4">
|
const Sidebar = ({
|
||||||
<div className="flex items-center space-x-2 mb-4">
|
connectionStatus,
|
||||||
<div
|
transportType,
|
||||||
className={`w-2 h-2 rounded-full ${
|
setTransportType,
|
||||||
connectionStatus === "connected"
|
command,
|
||||||
? "bg-green-500"
|
setCommand,
|
||||||
: connectionStatus === "error"
|
args,
|
||||||
? "bg-red-500"
|
setArgs,
|
||||||
: "bg-gray-500"
|
url,
|
||||||
}`}
|
setUrl,
|
||||||
/>
|
env,
|
||||||
<span className="text-sm text-gray-600">
|
setEnv,
|
||||||
{connectionStatus === "connected"
|
onConnect,
|
||||||
? "Connected"
|
stdErrNotifications,
|
||||||
: connectionStatus === "error"
|
}: SidebarProps) => {
|
||||||
? "Connection Error"
|
const [showEnvVars, setShowEnvVars] = useState(false);
|
||||||
: "Disconnected"}
|
|
||||||
</span>
|
return (
|
||||||
|
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
|
||||||
|
<div className="flex items-center p-4 border-b border-gray-200">
|
||||||
|
<Settings className="w-6 h-6 text-gray-500" />
|
||||||
|
<h1 className="ml-2 text-lg font-semibold">MCP Inspector</h1>
|
||||||
</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={url}
|
||||||
|
onChange={(e) => setUrl(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>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
|||||||
@@ -3,11 +3,18 @@ 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 { ListToolsResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import {
|
||||||
|
CallToolResult,
|
||||||
|
ListToolsResult,
|
||||||
|
Tool,
|
||||||
|
} 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";
|
||||||
|
|
||||||
|
import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
const ToolsTab = ({
|
const ToolsTab = ({
|
||||||
tools,
|
tools,
|
||||||
listTools,
|
listTools,
|
||||||
@@ -23,12 +30,59 @@ const ToolsTab = ({
|
|||||||
callTool: (name: string, params: Record<string, unknown>) => void;
|
callTool: (name: string, params: Record<string, unknown>) => void;
|
||||||
selectedTool: Tool | null;
|
selectedTool: Tool | null;
|
||||||
setSelectedTool: (tool: Tool) => void;
|
setSelectedTool: (tool: Tool) => void;
|
||||||
toolResult: string;
|
toolResult: CompatibilityCallToolResult | null;
|
||||||
nextCursor: ListToolsResult["nextCursor"];
|
nextCursor: ListToolsResult["nextCursor"];
|
||||||
error: string | null;
|
error: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const [params, setParams] = useState<Record<string, unknown>>({});
|
const [params, setParams] = useState<Record<string, unknown>>({});
|
||||||
|
|
||||||
|
const renderToolResult = () => {
|
||||||
|
if (!toolResult) return null;
|
||||||
|
|
||||||
|
if ("content" in toolResult) {
|
||||||
|
const structuredResult = toolResult as CallToolResult;
|
||||||
|
const isError = structuredResult.isError ?? false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4 className="font-semibold mb-2">
|
||||||
|
Tool Result: {isError ? "Error" : "Success"}
|
||||||
|
</h4>
|
||||||
|
{structuredResult.content.map((item, index) => (
|
||||||
|
<div key={index} className="mb-2">
|
||||||
|
{item.type === "text" && (
|
||||||
|
<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}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
{item.type === "image" && (
|
||||||
|
<img
|
||||||
|
src={`data:${item.mimeType};base64,${item.data}`}
|
||||||
|
alt="Tool result image"
|
||||||
|
className="max-w-full h-auto"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{item.type === "resource" && (
|
||||||
|
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-64">
|
||||||
|
{JSON.stringify(item.resource, null, 2)}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else if ("toolResult" in toolResult) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4 className="font-semibold mb-2">Tool Result (Legacy):</h4>
|
||||||
|
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-64">
|
||||||
|
{JSON.stringify(toolResult.toolResult, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabsContent value="tools" className="grid grid-cols-2 gap-4">
|
<TabsContent value="tools" className="grid grid-cols-2 gap-4">
|
||||||
<ListPane
|
<ListPane
|
||||||
@@ -48,7 +102,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"}
|
||||||
@@ -75,24 +129,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>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
@@ -100,14 +174,7 @@ const ToolsTab = ({
|
|||||||
<Send className="w-4 h-4 mr-2" />
|
<Send className="w-4 h-4 mr-2" />
|
||||||
Run Tool
|
Run Tool
|
||||||
</Button>
|
</Button>
|
||||||
{toolResult && (
|
{toolResult && renderToolResult()}
|
||||||
<>
|
|
||||||
<h4 className="font-semibold mb-2">Tool Result:</h4>
|
|
||||||
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-64">
|
|
||||||
{toolResult}
|
|
||||||
</pre>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Alert>
|
<Alert>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
||||||
@@ -16,8 +16,8 @@ const alertVariants = cva(
|
|||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
const Alert = React.forwardRef<
|
const Alert = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
@@ -29,8 +29,8 @@ const Alert = React.forwardRef<
|
|||||||
className={cn(alertVariants({ variant }), className)}
|
className={cn(alertVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
Alert.displayName = "Alert"
|
Alert.displayName = "Alert";
|
||||||
|
|
||||||
const AlertTitle = React.forwardRef<
|
const AlertTitle = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
@@ -41,8 +41,8 @@ const AlertTitle = React.forwardRef<
|
|||||||
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
AlertTitle.displayName = "AlertTitle"
|
AlertTitle.displayName = "AlertTitle";
|
||||||
|
|
||||||
const AlertDescription = React.forwardRef<
|
const AlertDescription = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
@@ -53,7 +53,7 @@ const AlertDescription = React.forwardRef<
|
|||||||
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
AlertDescription.displayName = "AlertDescription"
|
AlertDescription.displayName = "AlertDescription";
|
||||||
|
|
||||||
export { Alert, AlertTitle, AlertDescription }
|
export { Alert, AlertTitle, AlertDescription };
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
||||||
@@ -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:
|
||||||
@@ -31,27 +31,27 @@ const buttonVariants = cva(
|
|||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
VariantProps<typeof buttonVariants> {
|
VariantProps<typeof buttonVariants> {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : "button";
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
Button.displayName = "Button"
|
Button.displayName = "Button";
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
export { Button, buttonVariants };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export interface InputProps
|
export interface InputProps
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
@@ -12,14 +12,14 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
Input.displayName = "Input"
|
Input.displayName = "Input";
|
||||||
|
|
||||||
export { Input }
|
export { Input };
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const labelVariants = cva(
|
const labelVariants = cva(
|
||||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||||
)
|
);
|
||||||
|
|
||||||
const Label = React.forwardRef<
|
const Label = React.forwardRef<
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
@@ -18,7 +18,7 @@ const Label = React.forwardRef<
|
|||||||
className={cn(labelVariants(), className)}
|
className={cn(labelVariants(), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
Label.displayName = LabelPrimitive.Root.displayName
|
Label.displayName = LabelPrimitive.Root.displayName;
|
||||||
|
|
||||||
export { Label }
|
export { Label };
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
CaretSortIcon,
|
CaretSortIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ChevronUpIcon,
|
ChevronUpIcon,
|
||||||
} from "@radix-ui/react-icons"
|
} from "@radix-ui/react-icons";
|
||||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const Select = SelectPrimitive.Root
|
const Select = SelectPrimitive.Root;
|
||||||
|
|
||||||
const SelectGroup = SelectPrimitive.Group
|
const SelectGroup = SelectPrimitive.Group;
|
||||||
|
|
||||||
const SelectValue = SelectPrimitive.Value
|
const SelectValue = SelectPrimitive.Value;
|
||||||
|
|
||||||
const SelectTrigger = React.forwardRef<
|
const SelectTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||||
@@ -23,7 +23,7 @@ const SelectTrigger = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -32,8 +32,8 @@ const SelectTrigger = React.forwardRef<
|
|||||||
<CaretSortIcon className="h-4 w-4 opacity-50" />
|
<CaretSortIcon className="h-4 w-4 opacity-50" />
|
||||||
</SelectPrimitive.Icon>
|
</SelectPrimitive.Icon>
|
||||||
</SelectPrimitive.Trigger>
|
</SelectPrimitive.Trigger>
|
||||||
))
|
));
|
||||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
const SelectScrollUpButton = React.forwardRef<
|
const SelectScrollUpButton = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||||
@@ -43,14 +43,14 @@ const SelectScrollUpButton = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center justify-center py-1",
|
"flex cursor-default items-center justify-center py-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronUpIcon />
|
<ChevronUpIcon />
|
||||||
</SelectPrimitive.ScrollUpButton>
|
</SelectPrimitive.ScrollUpButton>
|
||||||
))
|
));
|
||||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
||||||
|
|
||||||
const SelectScrollDownButton = React.forwardRef<
|
const SelectScrollDownButton = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||||
@@ -60,15 +60,15 @@ const SelectScrollDownButton = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center justify-center py-1",
|
"flex cursor-default items-center justify-center py-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronDownIcon />
|
<ChevronDownIcon />
|
||||||
</SelectPrimitive.ScrollDownButton>
|
</SelectPrimitive.ScrollDownButton>
|
||||||
))
|
));
|
||||||
SelectScrollDownButton.displayName =
|
SelectScrollDownButton.displayName =
|
||||||
SelectPrimitive.ScrollDownButton.displayName
|
SelectPrimitive.ScrollDownButton.displayName;
|
||||||
|
|
||||||
const SelectContent = React.forwardRef<
|
const SelectContent = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
@@ -81,7 +81,7 @@ const SelectContent = React.forwardRef<
|
|||||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
position={position}
|
position={position}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -91,7 +91,7 @@ const SelectContent = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
"p-1",
|
"p-1",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -99,8 +99,8 @@ const SelectContent = React.forwardRef<
|
|||||||
<SelectScrollDownButton />
|
<SelectScrollDownButton />
|
||||||
</SelectPrimitive.Content>
|
</SelectPrimitive.Content>
|
||||||
</SelectPrimitive.Portal>
|
</SelectPrimitive.Portal>
|
||||||
))
|
));
|
||||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||||
|
|
||||||
const SelectLabel = React.forwardRef<
|
const SelectLabel = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
@@ -111,8 +111,8 @@ const SelectLabel = React.forwardRef<
|
|||||||
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||||
|
|
||||||
const SelectItem = React.forwardRef<
|
const SelectItem = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||||
@@ -122,7 +122,7 @@ const SelectItem = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -133,8 +133,8 @@ const SelectItem = React.forwardRef<
|
|||||||
</span>
|
</span>
|
||||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
</SelectPrimitive.Item>
|
</SelectPrimitive.Item>
|
||||||
))
|
));
|
||||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||||
|
|
||||||
const SelectSeparator = React.forwardRef<
|
const SelectSeparator = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
@@ -145,8 +145,8 @@ const SelectSeparator = React.forwardRef<
|
|||||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Select,
|
Select,
|
||||||
@@ -159,4 +159,4 @@ export {
|
|||||||
SelectSeparator,
|
SelectSeparator,
|
||||||
SelectScrollUpButton,
|
SelectScrollUpButton,
|
||||||
SelectScrollDownButton,
|
SelectScrollDownButton,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const Tabs = TabsPrimitive.Root
|
const Tabs = TabsPrimitive.Root;
|
||||||
|
|
||||||
const TabsList = React.forwardRef<
|
const TabsList = React.forwardRef<
|
||||||
React.ElementRef<typeof TabsPrimitive.List>,
|
React.ElementRef<typeof TabsPrimitive.List>,
|
||||||
@@ -13,12 +13,12 @@ const TabsList = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TabsList.displayName = TabsPrimitive.List.displayName
|
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||||
|
|
||||||
const TabsTrigger = React.forwardRef<
|
const TabsTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||||
@@ -27,13 +27,13 @@ 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}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
const TabsContent = React.forwardRef<
|
const TabsContent = React.forwardRef<
|
||||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||||
@@ -43,11 +43,11 @@ const TabsContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export interface TextareaProps
|
export interface TextareaProps
|
||||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||||
@@ -11,14 +11,14 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|||||||
<textarea
|
<textarea
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
Textarea.displayName = "Textarea"
|
Textarea.displayName = "Textarea";
|
||||||
|
|
||||||
export { Textarea }
|
export { Textarea };
|
||||||
|
|||||||
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>;
|
||||||
29
client/src/lib/useDarkModeSync.ts
Normal file
29
client/src/lib/useDarkModeSync.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
// Listen for changes to the user's preferred color scheme
|
||||||
|
const useDarkModeSync = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
const darkModeMediaQuery = window.matchMedia(
|
||||||
|
"(prefers-color-scheme: dark)",
|
||||||
|
);
|
||||||
|
const handleDarkModeChange = (e: MediaQueryListEvent) => {
|
||||||
|
if (e.matches) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (darkModeMediaQuery.matches) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
}
|
||||||
|
|
||||||
|
darkModeMediaQuery.addEventListener("change", handleDarkModeChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
darkModeMediaQuery.removeEventListener("change", handleDarkModeChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDarkModeSync;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { clsx, type ClassValue } from "clsx"
|
import { clsx, type ClassValue } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": ["./src/*"]
|
||||||
"./src/*"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
|
|||||||
186
package-lock.json
generated
186
package-lock.json
generated
@@ -1,25 +1,36 @@
|
|||||||
{
|
{
|
||||||
"name": "mcp-inspector",
|
"name": "@modelcontextprotocol/inspector",
|
||||||
"version": "0.0.1",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mcp-inspector",
|
"name": "@modelcontextprotocol/inspector",
|
||||||
"version": "0.0.1",
|
"version": "0.2.0",
|
||||||
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"client",
|
"client",
|
||||||
"server"
|
"server"
|
||||||
],
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/inspector-client": "0.2.0",
|
||||||
|
"@modelcontextprotocol/inspector-server": "0.2.0",
|
||||||
|
"concurrently": "^9.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mcp-inspector": "bin/cli.js"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^9.0.1",
|
"@types/node": "^22.7.5",
|
||||||
"prettier": "3.3.3"
|
"prettier": "3.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"version": "0.0.0",
|
"name": "@modelcontextprotocol/inspector-client",
|
||||||
|
"version": "0.2.0",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "*",
|
"@modelcontextprotocol/sdk": "0.7.0",
|
||||||
"@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",
|
||||||
@@ -30,15 +41,21 @@
|
|||||||
"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",
|
||||||
"tailwind-merge": "^2.5.3",
|
"tailwind-merge": "^2.5.3",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
|
"bin": {
|
||||||
|
"mcp-inspector-client": "bin/cli.js"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.11.1",
|
"@eslint/js": "^9.11.1",
|
||||||
"@types/node": "^22.7.5",
|
"@types/node": "^22.7.5",
|
||||||
"@types/react": "^18.3.10",
|
"@types/react": "^18.3.10",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@types/serve-handler": "^6.1.4",
|
||||||
"@vitejs/plugin-react": "^4.3.2",
|
"@vitejs/plugin-react": "^4.3.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.11.1",
|
"eslint": "^9.11.1",
|
||||||
@@ -840,10 +857,18 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/inspector-client": {
|
||||||
|
"resolved": "client",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/inspector-server": {
|
||||||
|
"resolved": "server",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@modelcontextprotocol/sdk": {
|
"node_modules/@modelcontextprotocol/sdk": {
|
||||||
"version": "0.1.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-internal/@modelcontextprotocol/sdk/-/@modelcontextprotocol/sdk-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.7.0.tgz",
|
||||||
"integrity": "sha512-46/FTHNZWUWbdFspKsCIixhCKwi9Hub5+HWNXC1DRIL2TSV8cdx5sTsg+Jy6I4Uc8/rd7tLZcVQ6IasIY1g4zg==",
|
"integrity": "sha512-YlnQf8//eDHClUM607vb/6+GHmCdMnIfOkN2pcpexN4go9sYHm2JfNnqc5ILS7M8enUlwe9dQO9886l3NO3rUw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^1.0.5",
|
||||||
"raw-body": "^3.0.0",
|
"raw-body": "^3.0.0",
|
||||||
@@ -1733,6 +1758,16 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/serve-handler": {
|
||||||
|
"version": "6.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/serve-handler/-/serve-handler-6.1.4.tgz",
|
||||||
|
"integrity": "sha512-aXy58tNie0NkuSCY291xUxl0X+kGYy986l4kqW6Gi4kEXgr6Tx0fpSH7YwUSa5usPpG3s9DBeIR6hHcDtL2IvQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/serve-static": {
|
"node_modules/@types/serve-static": {
|
||||||
"version": "1.15.7",
|
"version": "1.15.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
|
||||||
@@ -2245,7 +2280,6 @@
|
|||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
@@ -2369,7 +2403,6 @@
|
|||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.1.0",
|
"ansi-styles": "^4.1.0",
|
||||||
@@ -2427,15 +2460,10 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/client": {
|
|
||||||
"resolved": "client",
|
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/cliui": {
|
"node_modules/cliui": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"string-width": "^4.2.0",
|
"string-width": "^4.2.0",
|
||||||
@@ -2486,14 +2514,12 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/concurrently": {
|
"node_modules/concurrently": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz",
|
||||||
"integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==",
|
"integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
@@ -2519,7 +2545,6 @@
|
|||||||
"version": "8.1.1",
|
"version": "8.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-flag": "^4.0.0"
|
"has-flag": "^4.0.0"
|
||||||
@@ -2797,7 +2822,6 @@
|
|||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -3340,7 +3364,6 @@
|
|||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "6.* || 8.* || >= 10.*"
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
@@ -3479,7 +3502,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -3843,7 +3865,6 @@
|
|||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
@@ -3884,10 +3905,6 @@
|
|||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mcp-inspector": {
|
|
||||||
"resolved": "server",
|
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@@ -3974,7 +3991,6 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
@@ -4199,6 +4215,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-is-inside": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="
|
||||||
|
},
|
||||||
"node_modules/path-key": {
|
"node_modules/path-key": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
@@ -4635,12 +4656,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-remove-scroll/node_modules/tslib": {
|
|
||||||
"version": "2.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
|
||||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
|
||||||
"license": "0BSD"
|
|
||||||
},
|
|
||||||
"node_modules/react-style-singleton": {
|
"node_modules/react-style-singleton": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
||||||
@@ -4664,6 +4679,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-toastify": {
|
||||||
|
"version": "10.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz",
|
||||||
|
"integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@@ -4689,7 +4716,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -4805,19 +4831,11 @@
|
|||||||
"version": "7.8.1",
|
"version": "7.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rxjs/node_modules/tslib": {
|
|
||||||
"version": "2.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
|
||||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "0BSD"
|
|
||||||
},
|
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@@ -4911,6 +4929,69 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/serve-handler": {
|
||||||
|
"version": "6.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz",
|
||||||
|
"integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.0.0",
|
||||||
|
"content-disposition": "0.5.2",
|
||||||
|
"mime-types": "2.1.18",
|
||||||
|
"minimatch": "3.1.2",
|
||||||
|
"path-is-inside": "1.0.2",
|
||||||
|
"path-to-regexp": "3.3.0",
|
||||||
|
"range-parser": "1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/bytes": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/content-disposition": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/mime-db": {
|
||||||
|
"version": "1.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
|
||||||
|
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/mime-types": {
|
||||||
|
"version": "2.1.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
|
||||||
|
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "~1.33.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/path-to-regexp": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw=="
|
||||||
|
},
|
||||||
|
"node_modules/serve-handler/node_modules/range-parser": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/serve-static": {
|
"node_modules/serve-static": {
|
||||||
"version": "1.16.2",
|
"version": "1.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||||
@@ -4974,7 +5055,6 @@
|
|||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
|
||||||
"integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
|
"integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -5121,7 +5201,6 @@
|
|||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-flag": "^4.0.0"
|
"has-flag": "^4.0.0"
|
||||||
@@ -5273,7 +5352,6 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||||
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tree-kill": "cli.js"
|
"tree-kill": "cli.js"
|
||||||
@@ -5658,7 +5736,6 @@
|
|||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
@@ -5715,7 +5792,6 @@
|
|||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -5744,7 +5820,6 @@
|
|||||||
"version": "17.7.2",
|
"version": "17.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cliui": "^8.0.1",
|
"cliui": "^8.0.1",
|
||||||
@@ -5763,7 +5838,6 @@
|
|||||||
"version": "21.1.1",
|
"version": "21.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -5792,16 +5866,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"name": "mcp-inspector",
|
"name": "@modelcontextprotocol/inspector-server",
|
||||||
"version": "0.0.1",
|
"version": "0.2.0",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "*",
|
"@modelcontextprotocol/sdk": "0.7.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^2.0.2",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
"ws": "^8.18.0",
|
"ws": "^8.18.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
|
"bin": {
|
||||||
|
"mcp-inspector-server": "build/index.js"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/eventsource": "^1.1.15",
|
"@types/eventsource": "^1.1.15",
|
||||||
|
|||||||
34
package.json
34
package.json
@@ -1,8 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "mcp-inspector",
|
"name": "@modelcontextprotocol/inspector",
|
||||||
"private": true,
|
"version": "0.2.1",
|
||||||
"version": "0.0.1",
|
"description": "Model Context Protocol inspector",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
"homepage": "https://modelcontextprotocol.io",
|
||||||
|
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"mcp-inspector": "./bin/cli.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin",
|
||||||
|
"client/bin",
|
||||||
|
"client/dist",
|
||||||
|
"server/build"
|
||||||
|
],
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"client",
|
"client",
|
||||||
"server"
|
"server"
|
||||||
@@ -14,11 +27,18 @@
|
|||||||
"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": "concurrently \"npm run start-server\" \"npm run start-client\"",
|
"start": "./bin/cli.js",
|
||||||
"prettier-fix": "prettier --write ."
|
"prepare": "npm run build",
|
||||||
|
"prettier-fix": "prettier --write .",
|
||||||
|
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/inspector-client": "0.2.1",
|
||||||
|
"@modelcontextprotocol/inspector-server": "0.2.1",
|
||||||
|
"concurrently": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^9.0.1",
|
"prettier": "3.3.3",
|
||||||
"prettier": "3.3.3"
|
"@types/node": "^22.7.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "mcp-inspector",
|
"name": "@modelcontextprotocol/inspector-server",
|
||||||
"version": "0.0.1",
|
"version": "0.2.1",
|
||||||
"main": "build/index.js",
|
"description": "Server-side application for the Model Context Protocol inspector",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
"homepage": "https://modelcontextprotocol.io",
|
||||||
|
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"mcp-inspector-server": "build/index.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"build"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node build/index.js",
|
"start": "node build/index.js",
|
||||||
@@ -17,7 +27,7 @@
|
|||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "*",
|
"@modelcontextprotocol/sdk": "0.7.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^2.0.2",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
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 { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
import {
|
||||||
|
StdioClientTransport,
|
||||||
|
getDefaultEnvironment,
|
||||||
|
} from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||||
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";
|
||||||
@@ -11,6 +17,14 @@ import mcpProxy from "./mcpProxy.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());
|
||||||
|
|
||||||
@@ -24,8 +38,16 @@ 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 args = (query.args as string).split(/\s+/);
|
||||||
console.log(`Stdio transport: command=${command}, args=${args}`);
|
const env = query.env ? JSON.parse(query.env as string) : undefined;
|
||||||
const transport = new StdioClientTransport({ command, args });
|
console.log(
|
||||||
|
`Stdio transport: command=${command}, args=${args}, env=${JSON.stringify(env)}`,
|
||||||
|
);
|
||||||
|
const transport = new StdioClientTransport({
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
env,
|
||||||
|
stderr: "pipe",
|
||||||
|
});
|
||||||
await transport.start();
|
await transport.start();
|
||||||
console.log("Spawned stdio transport");
|
console.log("Spawned stdio transport");
|
||||||
return transport;
|
return transport;
|
||||||
@@ -43,43 +65,77 @@ 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/config", (req, res) => {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
await transport.handlePostMessage(req, res);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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